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;
+ }
+}