Merge "Flesh out streaming service management in service"
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/AppActiveStreams.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/AppActiveStreams.java
new file mode 100644
index 0000000..f37a340
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/AppActiveStreams.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.testapps.embmsmw;
+
+import android.os.RemoteException;
+import android.telephony.mbms.IStreamingServiceCallback;
+import android.telephony.mbms.StreamingService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// Tracks the states of the streams for a single (uid, appName, subscriptionId) tuple
+public class AppActiveStreams {
+    // Wrapper for a pair (StreamingServiceCallback, streaming state)
+    private static class StreamCallbackWithState {
+        private final IStreamingServiceCallback mCallback;
+        private int mState;
+
+        public StreamCallbackWithState(IStreamingServiceCallback callback, int state) {
+            mCallback = callback;
+            mState = state;
+        }
+
+        public IStreamingServiceCallback getCallback() {
+            return mCallback;
+        }
+
+        public int getState() {
+            return mState;
+        }
+
+        public void setState(int state) {
+            mState = state;
+        }
+    }
+
+    // Stores the state and callback per service ID.
+    private final Map<String, StreamCallbackWithState> mStreamStates = new HashMap<>();
+    private final StreamingAppIdentifier mAppIdentifier;
+
+    public AppActiveStreams(StreamingAppIdentifier appIdentifier) {
+        mAppIdentifier = appIdentifier;
+    }
+
+    public int getStateForService(String serviceId) {
+        StreamCallbackWithState callbackWithState = mStreamStates.get(serviceId);
+        return callbackWithState == null ?
+                StreamingService.STATE_STOPPED : callbackWithState.getState();
+    }
+
+    public void startStreaming(String serviceId, IStreamingServiceCallback callback) {
+        mStreamStates.put(serviceId,
+                new StreamCallbackWithState(callback, StreamingService.STATE_STARTED));
+        try {
+            callback.streamStateChanged(StreamingService.STATE_STARTED);
+        } catch (RemoteException e) {
+            dispose(serviceId);
+        }
+    }
+
+    public void stopStreaming(String serviceId) {
+        StreamCallbackWithState entry = mStreamStates.get(serviceId);
+
+        if (entry != null) {
+            try {
+                entry.setState(StreamingService.STATE_STOPPED);
+                entry.getCallback().streamStateChanged(StreamingService.STATE_STOPPED);
+            } catch (RemoteException e) {
+                dispose(serviceId);
+            }
+        }
+    }
+
+    public void dispose(String serviceId) {
+        mStreamStates.remove(serviceId);
+    }
+}
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsTestStreamingService.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsTestStreamingService.java
index 11f4e6d..0693292 100644
--- a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsTestStreamingService.java
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsTestStreamingService.java
@@ -16,17 +16,18 @@
 
 package com.android.phone.testapps.embmsmw;
 
-import android.app.AppOpsManager;
 import android.app.Service;
-import android.content.Context;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IStreamingServiceCallback;
 import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.StreamingService;
 import android.telephony.mbms.StreamingServiceInfo;
 import android.telephony.mbms.vendor.IMbmsStreamingService;
 import android.telephony.mbms.vendor.MbmsStreamingServiceBase;
@@ -35,12 +36,9 @@
 import com.android.internal.os.SomeArgs;
 
 import java.util.Arrays;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -50,6 +48,10 @@
     }};
 
     private static final String TAG = "EmbmsTestStreaming";
+
+    private static final long SEND_SERVICE_LIST_DELAY = 300;
+    private static final long START_STREAMING_DELAY = 500;
+
     private static final int SEND_STREAMING_SERVICES_LIST = 1;
 
     private final Map<StreamingAppIdentifier, IMbmsStreamingManagerCallback> mAppCallbacks =
@@ -71,6 +73,7 @@
                         // Assume app has gone away and clean up.
                     }
                 }
+                break;
         }
         return true;
     };
@@ -80,11 +83,12 @@
         public int initialize(IMbmsStreamingManagerCallback listener, String appName, int subId) {
             String[] packageNames = getPackageManager().getPackagesForUid(Binder.getCallingUid());
             if (packageNames == null) {
-                return MbmsException.ERROR_APP_PERMISSIONS_NOT_GRANTED;
+                throw new SecurityException("No matching packages found for your UID");
             }
             boolean isUidAllowed = Arrays.stream(packageNames).anyMatch(ALLOWED_PACKAGES::contains);
             if (!isUidAllowed) {
-                return MbmsException.ERROR_APP_PERMISSIONS_NOT_GRANTED;
+                throw new SecurityException("No packages for your UID are allowed to use this " +
+                        "service");
             }
 
             StreamingAppIdentifier appKey =
@@ -102,26 +106,10 @@
                 List<String> serviceClasses) {
             StreamingAppIdentifier appKey =
                     new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
-            if (!mAppCallbacks.containsKey(appKey)) {
-                return MbmsException.ERROR_NOT_YET_INITIALIZED;
-            }
+            checkInitialized(appKey);
 
-            Map<Locale, String> nameDict1 = new HashMap<Locale, String>() {{
-                put(Locale.US, "TestService1");
-            }};
-            Map<Locale, String> nameDict2 = new HashMap<Locale, String>() {{
-                put(Locale.US, "TestService1");
-            }};
-            StreamingServiceInfo info1 = new StreamingServiceInfo(nameDict1, "Class 1", Locale.US,
-                    "Service ID 1", new Date(System.currentTimeMillis() - 10000),
-                    new Date(System.currentTimeMillis() + 10000));
-            StreamingServiceInfo info2 = new StreamingServiceInfo(nameDict2, "Class 2", Locale.US,
-                    "Service ID 2", new Date(System.currentTimeMillis() - 20000),
-                    new Date(System.currentTimeMillis() + 20000));
-            List<StreamingServiceInfo> serviceInfos = new LinkedList<StreamingServiceInfo>() {{
-                add(info1);
-                add(info2);
-            }};
+            List<StreamingServiceInfo> serviceInfos =
+                    StreamingServiceRepository.getStreamingServicesForClasses(serviceClasses);
 
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = appKey;
@@ -129,9 +117,65 @@
 
             mHandler.removeMessages(SEND_STREAMING_SERVICES_LIST);
             mHandler.sendMessageDelayed(
-                    mHandler.obtainMessage(SEND_STREAMING_SERVICES_LIST, args), 300);
+                    mHandler.obtainMessage(SEND_STREAMING_SERVICES_LIST, args),
+                    SEND_SERVICE_LIST_DELAY);
             return MbmsException.SUCCESS;
         }
+
+        @Override
+        public int startStreaming(String appName, int subscriptionId, String serviceId,
+                IStreamingServiceCallback callback) {
+            StreamingAppIdentifier appKey =
+                    new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+            checkInitialized(appKey);
+            checkServiceExists(serviceId);
+
+            if (StreamStateTracker.getStreamingState(appKey, serviceId) ==
+                    StreamingService.STATE_STARTED) {
+                return MbmsException.ERROR_STREAM_ALREADY_STARTED;
+            }
+
+            mHandler.postDelayed(
+                    () -> StreamStateTracker.startStreaming(appKey, serviceId, callback),
+                    START_STREAMING_DELAY);
+            return MbmsException.SUCCESS;
+        }
+
+        @Override
+        public Uri getPlaybackUri(String appName, int subscriptionId, String serviceId) {
+            StreamingAppIdentifier appKey =
+                    new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+            checkInitialized(appKey);
+            checkServiceExists(serviceId);
+
+            Uri streamingUri = StreamingServiceRepository.getUriForService(serviceId);
+            if (streamingUri == null) {
+                throw new IllegalArgumentException("Invalid service ID");
+            }
+            return streamingUri;
+        }
+
+        @Override
+        public void disposeStream(String appName, int subscriptionId, String serviceId) {
+            StreamingAppIdentifier appKey =
+                    new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+            checkInitialized(appKey);
+            checkServiceExists(serviceId);
+
+            Log.i(TAG, "Disposing of stream " + serviceId);
+            StreamStateTracker.dispose(appKey, serviceId);
+        }
+
+        @Override
+        public void dispose(String appName, int subscriptionId) {
+            StreamingAppIdentifier appKey =
+                    new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+            checkInitialized(appKey);
+
+            Log.i(TAG, "Disposing app " + appName);
+            StreamStateTracker.disposeAll(appKey);
+            mAppCallbacks.remove(appKey);
+        }
     };
 
     @Override
@@ -153,4 +197,16 @@
     private static void logd(String s) {
         Log.d(TAG, s);
     }
+
+    private void checkInitialized(StreamingAppIdentifier appKey) {
+        if (!mAppCallbacks.containsKey(appKey)) {
+            throw new IllegalStateException("Not yet initialized");
+        }
+    }
+
+    private void checkServiceExists(String serviceId) {
+        if (StreamingServiceRepository.getStreamingServiceInfoForId(serviceId) == null) {
+            throw new IllegalArgumentException("Invalid service ID");
+        }
+    }
 }
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamStateTracker.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamStateTracker.java
new file mode 100644
index 0000000..c5496e8
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamStateTracker.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.testapps.embmsmw;
+
+import android.telephony.mbms.IStreamingServiceCallback;
+import android.telephony.mbms.StreamingService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// Singleton that keeps track of streaming states for all apps using the middleware.
+public class StreamStateTracker {
+    private static final Map<StreamingAppIdentifier, AppActiveStreams>
+            sPerAppStreamStates = new HashMap<>();
+
+    public static int getStreamingState(StreamingAppIdentifier appIdentifier, String serviceId) {
+        AppActiveStreams appStreams = sPerAppStreamStates.get(appIdentifier);
+        if (appStreams == null) {
+            return StreamingService.STATE_STOPPED;
+        }
+        return appStreams.getStateForService(serviceId);
+    }
+
+    public static void startStreaming(StreamingAppIdentifier appIdentifier, String serviceId,
+            IStreamingServiceCallback callback) {
+        AppActiveStreams appStreams = sPerAppStreamStates.get(appIdentifier);
+        if (appStreams == null) {
+            appStreams = new AppActiveStreams(appIdentifier);
+            sPerAppStreamStates.put(appIdentifier, appStreams);
+        }
+
+        appStreams.startStreaming(serviceId, callback);
+    }
+
+    public static void stopStreaming(StreamingAppIdentifier appIdentifier, String serviceId) {
+        AppActiveStreams appStreams = sPerAppStreamStates.get(appIdentifier);
+        if (appStreams == null) {
+            // It was never started, so don't bother stopping.
+            return;
+        }
+        appStreams.stopStreaming(serviceId);
+    }
+
+    public static void dispose(StreamingAppIdentifier appIdentifier, String serviceId) {
+        AppActiveStreams appStreams = sPerAppStreamStates.get(appIdentifier);
+        if (appStreams == null) {
+            // We have no record of this app, so we can just move on.
+            return;
+        }
+        appStreams.dispose(serviceId);
+    }
+
+    public static void disposeAll(StreamingAppIdentifier appIdentifier) {
+        sPerAppStreamStates.remove(appIdentifier);
+    }
+
+    // Do not instantiate
+    private StreamStateTracker() {}
+}
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamingServiceRepository.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamingServiceRepository.java
new file mode 100644
index 0000000..789da39
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamingServiceRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.testapps.embmsmw;
+
+import android.net.Uri;
+import android.telephony.mbms.StreamingServiceInfo;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class StreamingServiceRepository {
+    private static final String STREAMING_SCHEME = "stream";
+    private static final String STREAMING_URI_SSP_PREFIX = "identifier/";
+
+    private static int sServiceIdCounter = 0;
+    private static final Map<String, StreamingServiceInfo> sIdToServiceInfo =
+            new HashMap<>();
+    private static final Map<String, Uri> sIdToStreamingUri = new HashMap<>();
+
+    static {
+        fetchStreamingServices();
+    }
+
+    public static List<StreamingServiceInfo> getStreamingServicesForClasses(
+            List<String> serviceClasses) {
+        return sIdToServiceInfo.values().stream()
+                .filter((info) -> serviceClasses.contains(info.getClassName()))
+                .collect(Collectors.toList());
+    }
+
+    public static Uri getUriForService(String serviceId) {
+        if (sIdToStreamingUri.containsKey(serviceId)) {
+            return sIdToStreamingUri.get(serviceId);
+        }
+        return null;
+    }
+
+    public static StreamingServiceInfo getStreamingServiceInfoForId(String serviceId) {
+        return sIdToServiceInfo.getOrDefault(serviceId, null);
+    }
+
+    private static void createStreamingService(String className) {
+        sServiceIdCounter++;
+        String id = "StreamingServiceId[" + sServiceIdCounter + "]";
+        Map<Locale, String> localeDict = new HashMap<Locale, String>() {{
+            put(Locale.US, "Entertainment Source " + sServiceIdCounter);
+        }};
+        StreamingServiceInfo info = new StreamingServiceInfo(localeDict, className, Locale.US,
+                id, new Date(System.currentTimeMillis() - 10000),
+                new Date(System.currentTimeMillis() + 10000));
+        sIdToServiceInfo.put(id, info);
+        sIdToStreamingUri.put(id, Uri.fromParts(STREAMING_SCHEME,
+                STREAMING_URI_SSP_PREFIX + sServiceIdCounter,
+                null));
+    }
+
+    private static void fetchStreamingServices() {
+        createStreamingService("Class1");
+        createStreamingService("Class2");
+        createStreamingService("Class3");
+        createStreamingService("Class4");
+        createStreamingService("Class5");
+        createStreamingService("Class6");
+    }
+
+    // Do not instantiate
+    private StreamingServiceRepository() {}
+}
diff --git a/testapps/EmbmsTestStreamingApp/res/layout/activity_main.xml b/testapps/EmbmsTestStreamingApp/res/layout/activity_main.xml
index 028c443..47e9807 100644
--- a/testapps/EmbmsTestStreamingApp/res/layout/activity_main.xml
+++ b/testapps/EmbmsTestStreamingApp/res/layout/activity_main.xml
@@ -21,10 +21,19 @@
     android:layout_height="match_parent"
     android:orientation="vertical" >
     <TextView
-        android:id="@+id/curr_streaming_uri"
+        android:id="@+id/curr_streaming_uri_label"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:label="@string/streaming_uri_label"/>
+        android:text="@string/streaming_uri_label"/>
+    <TextView
+        android:id="@+id/curr_streaming_uri"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+    <Spinner
+        android:id="@+id/available_streaming_services"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:label="@string/available_streaming_services_label"/>
     <GridLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
@@ -40,10 +49,20 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/get_streaming_services_button" />
+        <Button
+            android:id="@+id/start_streaming_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/start_streaming_button" />
+        <Button
+            android:id="@+id/dispose_stream_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/dispose_stream_button" />
+        <Button
+            android:id="@+id/dispose_manager_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/dispose_manager_button" />
     </GridLayout>
-    <Spinner
-        android:id="@+id/available_streaming_services"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:label="@string/available_streaming_services_label"/>
 </LinearLayout>
diff --git a/testapps/EmbmsTestStreamingApp/res/values/donottranslate_strings.xml b/testapps/EmbmsTestStreamingApp/res/values/donottranslate_strings.xml
index 4895b2d..58518cd 100644
--- a/testapps/EmbmsTestStreamingApp/res/values/donottranslate_strings.xml
+++ b/testapps/EmbmsTestStreamingApp/res/values/donottranslate_strings.xml
@@ -19,5 +19,8 @@
     <string name="bind_button">Bind to service</string>
     <string name="streaming_uri_label">Current Streaming URI</string>
     <string name="get_streaming_services_button">Get streaming services</string>
+    <string name="start_streaming_button">Start Streaming</string>
     <string name="available_streaming_services_label">Available Streaming Services</string>
+    <string name="dispose_stream_button">Dispose latest stream</string>
+    <string name="dispose_manager_button">Dispose streaming manager</string>
 </resources>
\ No newline at end of file
diff --git a/testapps/EmbmsTestStreamingApp/src/com/android/phone/testapps/embmsfrontend/EmbmsTestStreamingApp.java b/testapps/EmbmsTestStreamingApp/src/com/android/phone/testapps/embmsfrontend/EmbmsTestStreamingApp.java
index 8369fde..df6396e 100644
--- a/testapps/EmbmsTestStreamingApp/src/com/android/phone/testapps/embmsfrontend/EmbmsTestStreamingApp.java
+++ b/testapps/EmbmsTestStreamingApp/src/com/android/phone/testapps/embmsfrontend/EmbmsTestStreamingApp.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -34,7 +35,10 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class EmbmsTestStreamingApp extends Activity {
     private static final String APP_NAME = "StreamingApp1";
@@ -60,7 +64,7 @@
         public View getView(int position, View convertView, ViewGroup parent) {
             StreamingServiceInfo info = getItem(position);
             TextView result = new TextView(EmbmsTestStreamingApp.this);
-            result.setText(info.getClassName());
+            result.setText(info.getNames().get(info.getLocale()));
             return result;
         }
 
@@ -68,8 +72,8 @@
         public View getDropDownView(int position, View convertView, ViewGroup parent) {
             StreamingServiceInfo info = getItem(position);
             TextView result = new TextView(EmbmsTestStreamingApp.this);
-            String text = "classname="
-                    + info.getClassName()
+            String text = "name="
+                    + info.getNames().get(info.getLocale())
                     + ", "
                     + "serviceId="
                     + info.getServiceId();
@@ -87,8 +91,11 @@
 
     private Handler mHandler;
     private HandlerThread mHandlerThread;
+    private StreamingServiceTracker mLatestStream = null;
 
     private StreamingServiceInfoAdapter mStreamingServicesDisplayAdapter;
+    private final Map<String, StreamingServiceTracker> mStreamingServiceTrackerById =
+            new HashMap<>();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -128,7 +135,7 @@
                 return;
             }
             try {
-                mStreamingManager.getStreamingServices(null);
+                mStreamingManager.getStreamingServices(Collections.singletonList("Class1"));
             } catch (MbmsException e) {
                 Toast.makeText(EmbmsTestStreamingApp.this,
                         "Error getting streaming services" + e.getErrorCode(),
@@ -136,7 +143,7 @@
             }
         });
 
-        Spinner serviceSelector = (Spinner) findViewById(R.id.available_streaming_services);
+        final Spinner serviceSelector = (Spinner) findViewById(R.id.available_streaming_services);
         mStreamingServicesDisplayAdapter.setDropDownViewResource(
                 android.R.layout.simple_spinner_dropdown_item);
         serviceSelector.setAdapter(mStreamingServicesDisplayAdapter);
@@ -145,16 +152,57 @@
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                 StreamingServiceInfo info =
                         (StreamingServiceInfo) serviceSelector.getItemAtPosition(position);
-                Toast.makeText(EmbmsTestStreamingApp.this,
-                        "Service selected: " + info.getClassName(), Toast.LENGTH_SHORT).show();
-                // TODO: use this value for start streaming
+                String toastText = "Service selected: " + info.getNames().get(info.getLocale());
+                Toast.makeText(EmbmsTestStreamingApp.this, toastText, Toast.LENGTH_SHORT).show();
             }
 
             @Override
             public void onNothingSelected(AdapterView<?> parent) {
-
             }
         });
+
+        Button startStreamingButton = (Button) findViewById(R.id.start_streaming_button);
+        startStreamingButton.setOnClickListener((view) -> {
+            if (mStreamingManager == null) {
+                Toast.makeText(EmbmsTestStreamingApp.this,
+                        "No streaming service bound", Toast.LENGTH_SHORT).show();
+                return;
+            }
+            StreamingServiceInfo serviceInfo =
+                    (StreamingServiceInfo) serviceSelector.getSelectedItem();
+            if (serviceInfo == null) {
+                Toast.makeText(EmbmsTestStreamingApp.this,
+                        "No streaming service selected", Toast.LENGTH_SHORT).show();
+                return;
+            }
+
+            StreamingServiceTracker tracker = new StreamingServiceTracker(this, serviceInfo);
+            tracker.startStreaming(mStreamingManager);
+            mStreamingServiceTrackerById.put(serviceInfo.getServiceId(), tracker);
+            mLatestStream = tracker;
+        });
+
+        Button disposeStreamButton = (Button) findViewById(R.id.dispose_stream_button);
+        disposeStreamButton.setOnClickListener((view) -> {
+            if (mLatestStream == null) {
+                Toast.makeText(EmbmsTestStreamingApp.this,
+                        "No streams active", Toast.LENGTH_SHORT).show();
+                return;
+            }
+            mLatestStream.dispose();
+            mStreamingServiceTrackerById.remove(mLatestStream.getServiceId());
+            TextView uriField = (TextView) findViewById(R.id.curr_streaming_uri);
+            uriField.setText("");
+            mLatestStream = null;
+        });
+
+        Button disposeManagerButton = (Button) findViewById(R.id.dispose_manager_button);
+        disposeManagerButton.setOnClickListener((view) -> {
+            TextView uriField = (TextView) findViewById(R.id.curr_streaming_uri);
+            uriField.setText("");
+            mStreamingServicesDisplayAdapter.update(Collections.emptyList());
+            mStreamingManager.dispose();
+        });
     }
 
     @Override
@@ -166,4 +214,11 @@
     private void updateStreamingServicesList(List<StreamingServiceInfo> services) {
         runOnUiThread(() -> mStreamingServicesDisplayAdapter.update(services));
     }
+
+    public void updateUriInUi(Uri uri) {
+        runOnUiThread(() -> {
+            TextView uriField = (TextView) findViewById(R.id.curr_streaming_uri);
+            uriField.setText(uri.toSafeString());
+        });
+    }
 }
diff --git a/testapps/EmbmsTestStreamingApp/src/com/android/phone/testapps/embmsfrontend/StreamingServiceTracker.java b/testapps/EmbmsTestStreamingApp/src/com/android/phone/testapps/embmsfrontend/StreamingServiceTracker.java
new file mode 100644
index 0000000..512c119
--- /dev/null
+++ b/testapps/EmbmsTestStreamingApp/src/com/android/phone/testapps/embmsfrontend/StreamingServiceTracker.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.testapps.embmsfrontend;
+
+import android.net.Uri;
+import android.telephony.MbmsStreamingManager;
+import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.StreamingService;
+import android.telephony.mbms.StreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
+import android.widget.Toast;
+
+public class StreamingServiceTracker {
+    private class Callback extends StreamingServiceCallback {
+        @Override
+        public void error(int errorCode, String message) {
+            String toastMessage = "Error: " + errorCode + ": " + message;
+            mActivity.runOnUiThread(() ->
+                    Toast.makeText(mActivity, toastMessage, Toast.LENGTH_SHORT).show());
+        }
+
+        @Override
+        public void streamStateChanged(int state) {
+            onStreamStateChanged(state);
+        }
+    }
+
+    private final EmbmsTestStreamingApp mActivity;
+    private final StreamingServiceInfo mStreamingServiceInfo;
+    private StreamingService mStreamingService;
+    private int mState;
+
+    public StreamingServiceTracker(EmbmsTestStreamingApp appActivity, StreamingServiceInfo info) {
+        mActivity = appActivity;
+        mStreamingServiceInfo = info;
+    }
+
+    public void startStreaming(MbmsStreamingManager streamingManager) {
+        try {
+            mStreamingService =
+                    streamingManager.startStreaming(mStreamingServiceInfo, new Callback());
+        } catch (MbmsException e) {
+            Toast.makeText(mActivity,
+                    "Error starting streaming" + e.getErrorCode(),
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    public void dispose() {
+        try {
+            mStreamingService.dispose();
+        } catch (MbmsException e) {
+            Toast.makeText(mActivity,
+                    "Error disposing stream" + e.getErrorCode(),
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    public String getServiceId() {
+        return mStreamingServiceInfo.getServiceId();
+    }
+
+    private void onStreamStateChanged(int state) {
+        String toastMessage = "Stream "
+                + mStreamingServiceInfo.getNames().get(mStreamingServiceInfo.getLocale())
+                + " has entered state "
+                + state;
+        mActivity.runOnUiThread(() ->
+                Toast.makeText(mActivity, toastMessage, Toast.LENGTH_SHORT).show());
+        if (state == StreamingService.STATE_STARTED && mState != StreamingService.STATE_STARTED) {
+            try {
+                Uri streamingUri = mStreamingService.getPlaybackUri();
+                mActivity.updateUriInUi(streamingUri);
+            } catch (MbmsException e) {
+                String errorToast = "Got error " + e.getErrorCode() + " while getting uri";
+                mActivity.runOnUiThread(() ->
+                        Toast.makeText(mActivity, errorToast, Toast.LENGTH_SHORT).show());
+            }
+        }
+        mState = state;
+    }
+}