DO NOT MERGE: Revert "DO NOT MERGE: Revert "Media: Add MediaRouterManager to control media route of other apps""
This reverts commit dd3f305bb3.
Reason for revert: wrong branch
Change-Id: Ic9b6d17af8e967ec73a9dac141c60e02195a8d19
This commit is contained in:
@@ -479,7 +479,10 @@ java_defaults {
|
||||
"media/java/android/media/IMediaHTTPConnection.aidl",
|
||||
"media/java/android/media/IMediaHTTPService.aidl",
|
||||
"media/java/android/media/IMediaResourceMonitor.aidl",
|
||||
"media/java/android/media/IMediaRoute2Callback.aidl",
|
||||
"media/java/android/media/IMediaRoute2Provider.aidl",
|
||||
"media/java/android/media/IMediaRouterClient.aidl",
|
||||
"media/java/android/media/IMediaRouter2ManagerClient.aidl",
|
||||
"media/java/android/media/IMediaRouterService.aidl",
|
||||
"media/java/android/media/IMediaScannerListener.aidl",
|
||||
"media/java/android/media/IMediaScannerService.aidl",
|
||||
@@ -1832,4 +1835,4 @@ aidl_mapping {
|
||||
name: "framework-aidl-mappings",
|
||||
srcs: [":framework-defaults"],
|
||||
output: "framework-aidl-mappings.txt"
|
||||
}
|
||||
}
|
||||
|
||||
24
media/java/android/media/IMediaRoute2Callback.aidl
Normal file
24
media/java/android/media/IMediaRoute2Callback.aidl
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2019 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 android.media;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IMediaRoute2Callback {
|
||||
void onRouteSelected(int uid, String routeId);
|
||||
}
|
||||
27
media/java/android/media/IMediaRoute2Provider.aidl
Normal file
27
media/java/android/media/IMediaRoute2Provider.aidl
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2019 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 android.media;
|
||||
|
||||
import android.media.IMediaRoute2Callback;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IMediaRoute2Provider {
|
||||
void setCallback(IMediaRoute2Callback callback);
|
||||
void selectRoute(int uid, String id);
|
||||
}
|
||||
25
media/java/android/media/IMediaRouter2ManagerClient.aidl
Normal file
25
media/java/android/media/IMediaRouter2ManagerClient.aidl
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2019 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 android.media;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IMediaRouter2ManagerClient {
|
||||
void onRouteSelected(int uid, String routeId);
|
||||
void onControlCategoriesChanged(int uid, in List<String> categories);
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package android.media;
|
||||
|
||||
import android.media.IMediaRouterClient;
|
||||
import android.media.IMediaRouter2ManagerClient;
|
||||
import android.media.MediaRouterClientState;
|
||||
|
||||
/**
|
||||
@@ -29,8 +30,15 @@ interface IMediaRouterService {
|
||||
MediaRouterClientState getState(IMediaRouterClient client);
|
||||
boolean isPlaybackActive(IMediaRouterClient client);
|
||||
|
||||
void setControlCategories(IMediaRouterClient client, in List<String> categories);
|
||||
void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
|
||||
void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
|
||||
void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
|
||||
void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction);
|
||||
|
||||
void registerManagerAsUser(IMediaRouter2ManagerClient callback,
|
||||
String packageName, int userId);
|
||||
void unregisterManager(IMediaRouter2ManagerClient callback);
|
||||
void setRemoteRoute(IMediaRouter2ManagerClient callback,
|
||||
int uid, String routeId, boolean explicit);
|
||||
}
|
||||
|
||||
108
media/java/android/media/MediaRoute2ProviderService.java
Normal file
108
media/java/android/media/MediaRoute2ProviderService.java
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2019 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 android.media;
|
||||
|
||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public abstract class MediaRoute2ProviderService extends Service {
|
||||
private static final String TAG = "MediaRouteProviderSrv";
|
||||
|
||||
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
|
||||
|
||||
private final Handler mHandler;
|
||||
private ProviderStub mStub;
|
||||
private IMediaRoute2Callback mCallback;
|
||||
|
||||
public MediaRoute2ProviderService() {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||
if (mStub == null) {
|
||||
mStub = new ProviderStub();
|
||||
}
|
||||
return mStub;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when selectRoute is called on a route of the provider.
|
||||
*
|
||||
* @param uid The target application uid
|
||||
* @param routeId The id of the target route
|
||||
*/
|
||||
public abstract void onSelect(int uid, String routeId);
|
||||
|
||||
/**
|
||||
* Updates provider info from selected route and appliation.
|
||||
*
|
||||
* TODO: When provider descriptor is defined, this should update the descriptor correctly.
|
||||
*
|
||||
* @param uid
|
||||
* @param routeId
|
||||
*/
|
||||
public void updateProvider(int uid, String routeId) {
|
||||
if (mCallback != null) {
|
||||
try {
|
||||
//TODO: After publishState() is fully implemented, delete this.
|
||||
mCallback.onRouteSelected(uid, routeId);
|
||||
} catch (RemoteException ex) {
|
||||
Log.d(TAG, "Failed to update provider");
|
||||
}
|
||||
}
|
||||
publishState();
|
||||
}
|
||||
|
||||
void setCallback(IMediaRoute2Callback callback) {
|
||||
mCallback = callback;
|
||||
publishState();
|
||||
}
|
||||
|
||||
void publishState() {
|
||||
//TODO: Send provider descriptor to the MediaRouterService
|
||||
}
|
||||
|
||||
final class ProviderStub extends IMediaRoute2Provider.Stub {
|
||||
ProviderStub() { }
|
||||
|
||||
@Override
|
||||
public void setCallback(IMediaRoute2Callback callback) {
|
||||
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::setCallback,
|
||||
MediaRoute2ProviderService.this, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectRoute(int uid, String id) {
|
||||
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelect,
|
||||
MediaRoute2ProviderService.this, uid, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,6 +347,17 @@ public class MediaRouter {
|
||||
return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
|
||||
}
|
||||
|
||||
void setControlCategories(List<String> categories) {
|
||||
if (mClient != null) {
|
||||
try {
|
||||
mMediaRouterService.setControlCategories(mClient,
|
||||
categories);
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(TAG, "Unable to set control categories.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePresentationDisplays(int changedDisplayId) {
|
||||
final int count = mRoutes.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -919,6 +930,25 @@ public class MediaRouter {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//TODO: Remove @hide when it is ready.
|
||||
//TODO: Provide pre-defined categories for app developers.
|
||||
/**
|
||||
* Sets control categories of the client application.
|
||||
* Control categories can be used to filter out media routes
|
||||
* that don't correspond with the client application.
|
||||
* The only routes that match any of the categories will be shown on other applications.
|
||||
*
|
||||
* @hide
|
||||
* @param categories Categories to set
|
||||
*/
|
||||
public void setControlCategories(@NonNull List<String> categories) {
|
||||
if (categories == null) {
|
||||
throw new IllegalArgumentException("Categories must not be null");
|
||||
}
|
||||
sStatic.setControlCategories(categories);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select the specified route to use for output of the given media types.
|
||||
* <p class="note">
|
||||
|
||||
241
media/java/android/media/MediaRouter2Manager.java
Normal file
241
media/java/android/media/MediaRouter2Manager.java
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2019 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 android.media;
|
||||
|
||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class MediaRouter2Manager {
|
||||
private static final String TAG = "MediaRouter2Manager";
|
||||
private static final Object sLock = new Object();
|
||||
|
||||
@GuardedBy("sLock")
|
||||
private static MediaRouter2Manager sInstance;
|
||||
|
||||
final String mPackageName;
|
||||
|
||||
private Context mContext;
|
||||
private Client mClient;
|
||||
private final IMediaRouterService mMediaRouterService;
|
||||
final Handler mHandler;
|
||||
|
||||
@GuardedBy("sLock")
|
||||
final ArrayList<CallbackRecord> mCallbacks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Gets an instance of media router manager that controls media route of other apps.
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static MediaRouter2Manager getInstance(@NonNull Context context) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("context must not be null");
|
||||
}
|
||||
synchronized (sLock) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new MediaRouter2Manager(context);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
|
||||
private MediaRouter2Manager(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
mMediaRouterService = IMediaRouterService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
|
||||
mPackageName = mContext.getPackageName();
|
||||
mHandler = new Handler(context.getMainLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to listen route info.
|
||||
*
|
||||
* @param executor The executor that runs the callback.
|
||||
* @param callback The callback to add.
|
||||
*/
|
||||
public void addCallback(@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull Callback callback) {
|
||||
|
||||
if (executor == null) {
|
||||
throw new IllegalArgumentException("executor must not be null");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
}
|
||||
|
||||
synchronized (sLock) {
|
||||
final int index = findCallbackRecord(callback);
|
||||
if (index >= 0) {
|
||||
Log.w(TAG, "Ignore adding the same callback twice.");
|
||||
return;
|
||||
}
|
||||
if (mCallbacks.size() == 0) {
|
||||
Client client = new Client();
|
||||
try {
|
||||
mMediaRouterService.registerManagerAsUser(client, mPackageName,
|
||||
UserHandle.myUserId());
|
||||
mClient = client;
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(TAG, "Unable to register media router manager.", ex);
|
||||
}
|
||||
}
|
||||
mCallbacks.add(new CallbackRecord(executor, callback));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified callback.
|
||||
*
|
||||
* @param callback The callback to remove.
|
||||
*/
|
||||
public void removeCallback(@NonNull Callback callback) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
}
|
||||
|
||||
synchronized (sLock) {
|
||||
final int index = findCallbackRecord(callback);
|
||||
if (index < 0) {
|
||||
Log.w(TAG, "Ignore removing unknown callback. " + callback);
|
||||
return;
|
||||
}
|
||||
mCallbacks.remove(index);
|
||||
if (mCallbacks.size() == 0 && mClient != null) {
|
||||
try {
|
||||
mMediaRouterService.unregisterManager(mClient);
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(TAG, "Unable to unregister media router manager", ex);
|
||||
}
|
||||
mClient = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int findCallbackRecord(Callback callback) {
|
||||
final int count = mCallbacks.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (mCallbacks.get(i).mCallback == callback) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects media route for the specified application uid.
|
||||
*
|
||||
* @param uid The uid of the application that should change it's media route.
|
||||
* @param routeId The id of the route to select
|
||||
*/
|
||||
public void selectRoute(int uid, String routeId) {
|
||||
if (mClient != null) {
|
||||
try {
|
||||
mMediaRouterService.setRemoteRoute(mClient, uid, routeId, /* explicit= */true);
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(TAG, "Unable to select media route", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselects media route for the specified application uid.
|
||||
*
|
||||
* @param uid The uid of the application that should stop routing.
|
||||
*/
|
||||
public void unselectRoute(int uid) {
|
||||
if (mClient != null) {
|
||||
try {
|
||||
mMediaRouterService.setRemoteRoute(mClient, uid, null, /* explicit= */ true);
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(TAG, "Unable to select media route", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void notifyRouteSelected(int uid, String routeId) {
|
||||
for (CallbackRecord record : mCallbacks) {
|
||||
record.mExecutor.execute(() -> record.mCallback.onRouteSelected(uid, routeId));
|
||||
}
|
||||
}
|
||||
|
||||
void notifyControlCategoriesChanged(int uid, List<String> categories) {
|
||||
for (CallbackRecord record : mCallbacks) {
|
||||
record.mExecutor.execute(
|
||||
() -> record.mCallback.onControlCategoriesChanged(uid, categories));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for receiving events about media routing changes.
|
||||
*/
|
||||
public abstract static class Callback {
|
||||
/**
|
||||
* Called when a route is selected for some application uid.
|
||||
* @param uid
|
||||
* @param routeId
|
||||
*/
|
||||
public abstract void onRouteSelected(int uid, String routeId);
|
||||
|
||||
/**
|
||||
* Called when the control categories of an application is changed.
|
||||
* @param uid the uid of the app that changed control categories
|
||||
* @param categories the changed categories
|
||||
*/
|
||||
public abstract void onControlCategoriesChanged(int uid, List<String> categories);
|
||||
}
|
||||
|
||||
final class CallbackRecord {
|
||||
public final Executor mExecutor;
|
||||
public final Callback mCallback;
|
||||
|
||||
CallbackRecord(Executor executor, Callback callback) {
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
class Client extends IMediaRouter2ManagerClient.Stub {
|
||||
@Override
|
||||
public void onRouteSelected(int uid, String routeId) {
|
||||
mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyRouteSelected,
|
||||
MediaRouter2Manager.this, uid, routeId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onControlCategoriesChanged(int uid, List<String> categories) {
|
||||
mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyControlCategoriesChanged,
|
||||
MediaRouter2Manager.this, uid, categories));
|
||||
}
|
||||
}
|
||||
}
|
||||
18
media/tests/MediaRouteProvider/Android.bp
Normal file
18
media/tests/MediaRouteProvider/Android.bp
Normal file
@@ -0,0 +1,18 @@
|
||||
android_test {
|
||||
name: "mediarouteprovider",
|
||||
|
||||
srcs: ["**/*.java"],
|
||||
|
||||
libs: [
|
||||
"android.test.runner",
|
||||
"android.test.base",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"android-support-test",
|
||||
"mockito-target-minus-junit4",
|
||||
],
|
||||
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
}
|
||||
30
media/tests/MediaRouteProvider/AndroidManifest.xml
Normal file
30
media/tests/MediaRouteProvider/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2019 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.mediarouteprovider.example">
|
||||
|
||||
<application android:label="@string/app_name">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<service android:name=".SampleMediaRoute2ProviderService"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.MediaRoute2ProviderService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
5
media/tests/MediaRouteProvider/res/values/strings.xml
Normal file
5
media/tests/MediaRouteProvider/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- name of the app [CHAR LIMIT=25]-->
|
||||
<string name="app_name">SampleMediaRouteProvider</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2019 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.mediarouteprovider.example;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.media.MediaRoute2ProviderService;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService {
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return super.onBind(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelect(int uid, String routeId) {
|
||||
updateProvider(uid, routeId);
|
||||
}
|
||||
}
|
||||
18
media/tests/MediaRouter/Android.bp
Normal file
18
media/tests/MediaRouter/Android.bp
Normal file
@@ -0,0 +1,18 @@
|
||||
android_test {
|
||||
name: "mediaroutertest",
|
||||
|
||||
srcs: ["**/*.java"],
|
||||
|
||||
libs: [
|
||||
"android.test.runner",
|
||||
"android.test.base",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"android-support-test",
|
||||
"mockito-target-minus-junit4",
|
||||
],
|
||||
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
}
|
||||
29
media/tests/MediaRouter/AndroidManifest.xml
Normal file
29
media/tests/MediaRouter/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2019 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.mediaroutertest">
|
||||
|
||||
<uses-permission android:name="android.permission.CONTROL_MEDIA_ROUTE" />
|
||||
|
||||
<application android:label="@string/app_name">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.mediaroutertest"
|
||||
android:label="MediaRouter Tests"/>
|
||||
</manifest>
|
||||
16
media/tests/MediaRouter/AndroidTest.xml
Normal file
16
media/tests/MediaRouter/AndroidTest.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<configuration description="Runs sample instrumentation test.">
|
||||
<target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
|
||||
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
|
||||
<option name="test-file-name" value="mediaroutertest.apk"/>
|
||||
</target_preparer>
|
||||
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
|
||||
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
|
||||
<option name="test-suite-tag" value="apct"/>
|
||||
<option name="test-tag" value="MediaRouterTest"/>
|
||||
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
|
||||
<option name="package" value="com.android.mediaroutertest"/>
|
||||
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
|
||||
<option name="hidden-api-checks" value="false"/>
|
||||
</test>
|
||||
</configuration>
|
||||
5
media/tests/MediaRouter/res/values/strings.xml
Normal file
5
media/tests/MediaRouter/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- name of the app [CHAR LIMIT=25]-->
|
||||
<string name="app_name">mediaRouterTest</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2019 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.mediaroutertest;
|
||||
|
||||
import static org.mockito.Mockito.after;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaRouter;
|
||||
import android.media.MediaRouter2Manager;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class MediaRouterManagerTest {
|
||||
private static final String TAG = "MediaRouterManagerTest";
|
||||
|
||||
private static final int TARGET_UID = 109992;
|
||||
private static final String ROUTE_1 = "MediaRoute1";
|
||||
|
||||
private static final int AWAIT_MS = 1000;
|
||||
private static final int TIMEOUT_MS = 1000;
|
||||
|
||||
private Context mContext;
|
||||
private MediaRouter2Manager mManager;
|
||||
private MediaRouter mRouter;
|
||||
private Executor mExecutor;
|
||||
|
||||
private static final List<String> TEST_CONTROL_CATEGORIES = new ArrayList();
|
||||
private static final String CONTROL_CATEGORY_1 = "android.media.mediarouter.MEDIA1";
|
||||
private static final String CONTROL_CATEGORY_2 = "android.media.mediarouter.MEDIA2";
|
||||
static {
|
||||
TEST_CONTROL_CATEGORIES.add(CONTROL_CATEGORY_1);
|
||||
TEST_CONTROL_CATEGORIES.add(CONTROL_CATEGORY_2);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
mManager = MediaRouter2Manager.getInstance(mContext);
|
||||
mRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
|
||||
mExecutor = new ThreadPoolExecutor(
|
||||
1, 20, 3, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<Runnable>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transferTest() throws Exception {
|
||||
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
|
||||
|
||||
mManager.addCallback(mExecutor, mockCallback);
|
||||
|
||||
verify(mockCallback, after(AWAIT_MS).never())
|
||||
.onRouteSelected(eq(TARGET_UID), any(String.class));
|
||||
|
||||
mManager.selectRoute(TARGET_UID, ROUTE_1);
|
||||
verify(mockCallback, timeout(TIMEOUT_MS)).onRouteSelected(TARGET_UID, ROUTE_1);
|
||||
|
||||
mManager.removeCallback(mockCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void controlCategoryTest() throws Exception {
|
||||
final int uid = android.os.Process.myUid();
|
||||
|
||||
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
|
||||
mManager.addCallback(mExecutor, mockCallback);
|
||||
|
||||
verify(mockCallback, after(AWAIT_MS).never()).onControlCategoriesChanged(eq(uid),
|
||||
any(List.class));
|
||||
|
||||
mRouter.setControlCategories(TEST_CONTROL_CATEGORIES);
|
||||
verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
|
||||
.onControlCategoriesChanged(uid, TEST_CONTROL_CATEGORIES);
|
||||
|
||||
mManager.removeCallback(mockCallback);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.media;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.IMediaRoute2Callback;
|
||||
import android.media.IMediaRoute2Provider;
|
||||
import android.media.MediaRoute2ProviderService;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.IBinder.DeathRecipient;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Maintains a connection to a particular media route provider service.
|
||||
*/
|
||||
final class MediaRoute2ProviderProxy implements ServiceConnection {
|
||||
private static final String TAG = "MediaRoute2ProviderProxy";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final Context mContext;
|
||||
private final ComponentName mComponentName;
|
||||
private final int mUserId;
|
||||
private final Handler mHandler;
|
||||
|
||||
private Callback mCallback;
|
||||
|
||||
// Selected Route info
|
||||
public int mSelectedUid;
|
||||
public String mSelectedRouteId;
|
||||
|
||||
// Connection state
|
||||
private boolean mRunning;
|
||||
private boolean mBound;
|
||||
private Connection mActiveConnection;
|
||||
private boolean mConnectionReady;
|
||||
|
||||
MediaRoute2ProviderProxy(Context context, ComponentName componentName, int userId) {
|
||||
mContext = context;
|
||||
mComponentName = componentName;
|
||||
mUserId = userId;
|
||||
mHandler = new Handler();
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
pw.println(prefix + "Proxy");
|
||||
pw.println(prefix + " mUserId=" + mUserId);
|
||||
pw.println(prefix + " mRunning=" + mRunning);
|
||||
pw.println(prefix + " mBound=" + mBound);
|
||||
pw.println(prefix + " mActiveConnection=" + mActiveConnection);
|
||||
pw.println(prefix + " mConnectionReady=" + mConnectionReady);
|
||||
}
|
||||
|
||||
public void setCallback(Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void setSelectedRoute(int uid, String routeId) {
|
||||
if (mConnectionReady) {
|
||||
mActiveConnection.selectRoute(uid, routeId);
|
||||
updateBinding();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasComponentName(String packageName, String className) {
|
||||
return mComponentName.getPackageName().equals(packageName)
|
||||
&& mComponentName.getClassName().equals(className);
|
||||
}
|
||||
|
||||
public String getFlattenedComponentName() {
|
||||
return mComponentName.flattenToShortString();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!mRunning) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Starting");
|
||||
}
|
||||
|
||||
mRunning = true;
|
||||
updateBinding();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mRunning) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Stopping");
|
||||
}
|
||||
|
||||
mRunning = false;
|
||||
updateBinding();
|
||||
}
|
||||
}
|
||||
|
||||
public void rebindIfDisconnected() {
|
||||
if (mActiveConnection == null && shouldBind()) {
|
||||
unbind();
|
||||
bind();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBinding() {
|
||||
if (shouldBind()) {
|
||||
bind();
|
||||
} else {
|
||||
unbind();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldBind() {
|
||||
//TODO: binding could be delayed until it's necessary.
|
||||
if (mRunning) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
if (!mBound) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Binding");
|
||||
}
|
||||
|
||||
Intent service = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE);
|
||||
service.setComponent(mComponentName);
|
||||
try {
|
||||
mBound = mContext.bindServiceAsUser(service, this,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
|
||||
new UserHandle(mUserId));
|
||||
if (!mBound && DEBUG) {
|
||||
Slog.d(TAG, this + ": Bind failed");
|
||||
}
|
||||
} catch (SecurityException ex) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Bind failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unbind() {
|
||||
if (mBound) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Unbinding");
|
||||
}
|
||||
|
||||
mBound = false;
|
||||
disconnect();
|
||||
mContext.unbindService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Connected");
|
||||
}
|
||||
|
||||
if (mBound) {
|
||||
disconnect();
|
||||
|
||||
IMediaRoute2Provider provider = IMediaRoute2Provider.Stub.asInterface(service);
|
||||
if (provider != null) {
|
||||
Connection connection = new Connection(provider);
|
||||
if (connection.register()) {
|
||||
mActiveConnection = connection;
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Registration failed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Slog.e(TAG, this + ": Service returned invalid remote display provider binder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Service disconnected");
|
||||
}
|
||||
disconnect();
|
||||
}
|
||||
|
||||
private void onConnectionReady(Connection connection) {
|
||||
if (mActiveConnection == connection) {
|
||||
mConnectionReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void onConnectionDied(Connection connection) {
|
||||
if (mActiveConnection == connection) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Service connection died");
|
||||
}
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void onRouteSelected(Connection connection, int uid, String routeId) {
|
||||
mSelectedUid = uid;
|
||||
mSelectedRouteId = routeId;
|
||||
|
||||
if (mActiveConnection == connection) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": State changed ");
|
||||
}
|
||||
mHandler.post(mStateChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
if (mActiveConnection != null) {
|
||||
mConnectionReady = false;
|
||||
mActiveConnection.dispose();
|
||||
mActiveConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Service connection " + mComponentName.flattenToShortString();
|
||||
}
|
||||
|
||||
private final Runnable mStateChanged = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mCallback != null) {
|
||||
mCallback.onProviderStateChanged(MediaRoute2ProviderProxy.this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public interface Callback {
|
||||
void onProviderStateChanged(MediaRoute2ProviderProxy provider);
|
||||
}
|
||||
|
||||
private final class Connection implements DeathRecipient {
|
||||
private final IMediaRoute2Provider mProvider;
|
||||
private final ProviderCallback mCallback;
|
||||
|
||||
Connection(IMediaRoute2Provider provider) {
|
||||
mProvider = provider;
|
||||
mCallback = new ProviderCallback(this);
|
||||
}
|
||||
|
||||
public boolean register() {
|
||||
try {
|
||||
mProvider.asBinder().linkToDeath(this, 0);
|
||||
mProvider.setCallback(mCallback);
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onConnectionReady(Connection.this);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} catch (RemoteException ex) {
|
||||
binderDied();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
mProvider.asBinder().unlinkToDeath(this, 0);
|
||||
mCallback.dispose();
|
||||
}
|
||||
|
||||
public void selectRoute(int uid, String id) {
|
||||
try {
|
||||
mProvider.selectRoute(uid, id);
|
||||
} catch (RemoteException ex) {
|
||||
Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onConnectionDied(Connection.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void postRouteSelected(int uid, String routeId) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onRouteSelected(Connection.this, uid, routeId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProviderCallback extends IMediaRoute2Callback.Stub {
|
||||
private final WeakReference<Connection> mConnectionRef;
|
||||
|
||||
ProviderCallback(Connection connection) {
|
||||
mConnectionRef = new WeakReference<Connection>(connection);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
mConnectionRef.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(int uid, String routeId) throws RemoteException {
|
||||
Connection connection = mConnectionRef.get();
|
||||
if (connection != null) {
|
||||
connection.postRouteSelected(uid, routeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.media;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.media.MediaRoute2ProviderService;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
*/
|
||||
final class MediaRoute2ProviderWatcher {
|
||||
private static final String TAG = "MediaRouteProvider"; // max. 23 chars
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final Context mContext;
|
||||
private final Callback mCallback;
|
||||
private final Handler mHandler;
|
||||
private final int mUserId;
|
||||
private final PackageManager mPackageManager;
|
||||
|
||||
private final ArrayList<MediaRoute2ProviderProxy> mProviders = new ArrayList<>();
|
||||
private boolean mRunning;
|
||||
|
||||
MediaRoute2ProviderWatcher(Context context,
|
||||
Callback callback, Handler handler, int userId) {
|
||||
mContext = context;
|
||||
mCallback = callback;
|
||||
mHandler = handler;
|
||||
mUserId = userId;
|
||||
mPackageManager = context.getPackageManager();
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
pw.println(prefix + "Watcher");
|
||||
pw.println(prefix + " mUserId=" + mUserId);
|
||||
pw.println(prefix + " mRunning=" + mRunning);
|
||||
pw.println(prefix + " mProviders.size()=" + mProviders.size());
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!mRunning) {
|
||||
mRunning = true;
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
|
||||
filter.addDataScheme("package");
|
||||
mContext.registerReceiverAsUser(mScanPackagesReceiver,
|
||||
new UserHandle(mUserId), filter, null, mHandler);
|
||||
|
||||
// Scan packages.
|
||||
// Also has the side-effect of restarting providers if needed.
|
||||
mHandler.post(mScanPackagesRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mRunning) {
|
||||
mRunning = false;
|
||||
|
||||
mContext.unregisterReceiver(mScanPackagesReceiver);
|
||||
mHandler.removeCallbacks(mScanPackagesRunnable);
|
||||
|
||||
// Stop all providers.
|
||||
for (int i = mProviders.size() - 1; i >= 0; i--) {
|
||||
mProviders.get(i).stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scanPackages() {
|
||||
if (!mRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add providers for all new services.
|
||||
// Reorder the list so that providers left at the end will be the ones to remove.
|
||||
int targetIndex = 0;
|
||||
Intent intent = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE);
|
||||
for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
|
||||
intent, 0, mUserId)) {
|
||||
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
|
||||
if (serviceInfo != null) {
|
||||
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
|
||||
if (sourceIndex < 0) {
|
||||
MediaRoute2ProviderProxy provider =
|
||||
new MediaRoute2ProviderProxy(mContext,
|
||||
new ComponentName(serviceInfo.packageName, serviceInfo.name),
|
||||
mUserId);
|
||||
provider.start();
|
||||
mProviders.add(targetIndex++, provider);
|
||||
mCallback.addProvider(provider);
|
||||
} else if (sourceIndex >= targetIndex) {
|
||||
MediaRoute2ProviderProxy provider = mProviders.get(sourceIndex);
|
||||
provider.start(); // restart the provider if needed
|
||||
provider.rebindIfDisconnected();
|
||||
Collections.swap(mProviders, sourceIndex, targetIndex++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove providers for missing services.
|
||||
if (targetIndex < mProviders.size()) {
|
||||
for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
|
||||
MediaRoute2ProviderProxy provider = mProviders.get(i);
|
||||
mCallback.removeProvider(provider);
|
||||
mProviders.remove(provider);
|
||||
provider.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int findProvider(String packageName, String className) {
|
||||
int count = mProviders.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MediaRoute2ProviderProxy provider = mProviders.get(i);
|
||||
if (provider.hasComponentName(packageName, className)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Received package manager broadcast: " + intent);
|
||||
}
|
||||
scanPackages();
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mScanPackagesRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scanPackages();
|
||||
}
|
||||
};
|
||||
|
||||
public interface Callback {
|
||||
void addProvider(MediaRoute2ProviderProxy provider);
|
||||
void removeProvider(MediaRoute2ProviderProxy provider);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import android.media.AudioRoutesInfo;
|
||||
import android.media.AudioSystem;
|
||||
import android.media.IAudioRoutesObserver;
|
||||
import android.media.IAudioService;
|
||||
import android.media.IMediaRouter2ManagerClient;
|
||||
import android.media.IMediaRouterClient;
|
||||
import android.media.IMediaRouterService;
|
||||
import android.media.MediaRouter;
|
||||
@@ -49,6 +50,7 @@ import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.IntArray;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TimeUtils;
|
||||
@@ -96,6 +98,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
private final Object mLock = new Object();
|
||||
private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
|
||||
private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
|
||||
private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
|
||||
private int mCurrentUserId = -1;
|
||||
private final IAudioService mAudioService;
|
||||
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
|
||||
@@ -302,6 +305,22 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
// Binder call
|
||||
@Override
|
||||
public void setControlCategories(IMediaRouterClient client, List<String> categories) {
|
||||
if (client == null) {
|
||||
throw new IllegalArgumentException("client must not be null");
|
||||
}
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
setControlCategoriesLocked(client, categories);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Binder call
|
||||
@Override
|
||||
public void setDiscoveryRequest(IMediaRouterClient client,
|
||||
@@ -402,6 +421,65 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
// Binder call
|
||||
@Override
|
||||
public void registerManagerAsUser(IMediaRouter2ManagerClient client,
|
||||
String packageName, int userId) {
|
||||
if (client == null) {
|
||||
throw new IllegalArgumentException("client must not be null");
|
||||
}
|
||||
//TODO: should check permission
|
||||
final boolean trusted = true;
|
||||
|
||||
final int uid = Binder.getCallingUid();
|
||||
if (!validatePackageName(uid, packageName)) {
|
||||
throw new SecurityException("packageName must match the calling uid");
|
||||
}
|
||||
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
|
||||
false /*allowAll*/, true /*requireFull*/, "registerManagerAsUser", packageName);
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
registerManagerLocked(client, uid, pid, packageName, resolvedUserId, trusted);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Binder call
|
||||
@Override
|
||||
public void unregisterManager(IMediaRouter2ManagerClient client) {
|
||||
if (client == null) {
|
||||
throw new IllegalArgumentException("client must not be null");
|
||||
}
|
||||
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
unregisterManagerLocked(client, false);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Binder call
|
||||
@Override
|
||||
public void setRemoteRoute(IMediaRouter2ManagerClient client,
|
||||
int uid, String routeId, boolean explicit) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
setRemoteRouteLocked(client, uid, routeId, explicit);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
void restoreBluetoothA2dp() {
|
||||
try {
|
||||
boolean a2dpOn;
|
||||
@@ -473,6 +551,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
void clientDied(ManagerRecord managerRecord) {
|
||||
synchronized (mLock) {
|
||||
unregisterManagerLocked(managerRecord.mClient, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerClientLocked(IMediaRouterClient client,
|
||||
int uid, int pid, String packageName, int userId, boolean trusted) {
|
||||
final IBinder binder = client.asBinder();
|
||||
@@ -520,6 +604,17 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setControlCategoriesLocked(IMediaRouterClient client, List<String> categories) {
|
||||
final IBinder binder = client.asBinder();
|
||||
ClientRecord clientRecord = mAllClientRecords.get(binder);
|
||||
|
||||
if (clientRecord != null) {
|
||||
clientRecord.mControlCategories = categories;
|
||||
clientRecord.mUserRecord.mHandler.obtainMessage(
|
||||
UserHandler.MSG_UPDATE_CLIENT_USAGE, clientRecord).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void setDiscoveryRequestLocked(IMediaRouterClient client,
|
||||
int routeTypes, boolean activeScan) {
|
||||
final IBinder binder = client.asBinder();
|
||||
@@ -573,6 +668,63 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
private void registerManagerLocked(IMediaRouter2ManagerClient client,
|
||||
int uid, int pid, String packageName, int userId, boolean trusted) {
|
||||
final IBinder binder = client.asBinder();
|
||||
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
|
||||
if (managerRecord == null) {
|
||||
boolean newUser = false;
|
||||
UserRecord userRecord = mUserRecords.get(userId);
|
||||
if (userRecord == null) {
|
||||
userRecord = new UserRecord(userId);
|
||||
newUser = true;
|
||||
}
|
||||
managerRecord = new ManagerRecord(userRecord, client, uid, pid, packageName, trusted);
|
||||
try {
|
||||
binder.linkToDeath(managerRecord, 0);
|
||||
} catch (RemoteException ex) {
|
||||
throw new RuntimeException("Media router client died prematurely.", ex);
|
||||
}
|
||||
|
||||
if (newUser) {
|
||||
mUserRecords.put(userId, userRecord);
|
||||
initializeUserLocked(userRecord);
|
||||
}
|
||||
|
||||
userRecord.mManagerRecords.add(managerRecord);
|
||||
mAllManagerRecords.put(binder, managerRecord);
|
||||
|
||||
// send client usage to manager
|
||||
final int clientCount = userRecord.mClientRecords.size();
|
||||
for (int i = 0; i < clientCount; i++) {
|
||||
userRecord.mHandler.obtainMessage(UserHandler.MSG_UPDATE_CLIENT_USAGE,
|
||||
userRecord.mClientRecords.get(i)).sendToTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterManagerLocked(IMediaRouter2ManagerClient client, boolean died) {
|
||||
ManagerRecord clientRecord = mAllManagerRecords.remove(client.asBinder());
|
||||
if (clientRecord != null) {
|
||||
UserRecord userRecord = clientRecord.mUserRecord;
|
||||
userRecord.mManagerRecords.remove(clientRecord);
|
||||
clientRecord.dispose();
|
||||
disposeUserIfNeededLocked(userRecord); // since client removed from user
|
||||
}
|
||||
}
|
||||
|
||||
private void setRemoteRouteLocked(IMediaRouter2ManagerClient client,
|
||||
int uid, String routeId, boolean explicit) {
|
||||
ManagerRecord managerRecord = mAllManagerRecords.get(client.asBinder());
|
||||
if (managerRecord != null) {
|
||||
if (explicit && managerRecord.mTrusted) {
|
||||
Pair<Integer, String> obj = new Pair<>(uid, routeId);
|
||||
managerRecord.mUserRecord.mHandler.obtainMessage(
|
||||
UserHandler.MSG_SELECT_REMOTE_ROUTE, obj).sendToTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void requestSetVolumeLocked(IMediaRouterClient client,
|
||||
String routeId, int volume) {
|
||||
final IBinder binder = client.asBinder();
|
||||
@@ -665,6 +817,46 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
final class ManagerRecord implements DeathRecipient {
|
||||
public final UserRecord mUserRecord;
|
||||
public final IMediaRouter2ManagerClient mClient;
|
||||
public final int mUid;
|
||||
public final int mPid;
|
||||
public final String mPackageName;
|
||||
public final boolean mTrusted;
|
||||
|
||||
ManagerRecord(UserRecord userRecord, IMediaRouter2ManagerClient client,
|
||||
int uid, int pid, String packageName, boolean trusted) {
|
||||
mUserRecord = userRecord;
|
||||
mClient = client;
|
||||
mUid = uid;
|
||||
mPid = pid;
|
||||
mPackageName = packageName;
|
||||
mTrusted = trusted;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
mClient.asBinder().unlinkToDeath(this, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
clientDied(this);
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
pw.println(prefix + this);
|
||||
|
||||
final String indent = prefix + " ";
|
||||
pw.println(indent + "mTrusted=" + mTrusted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Client " + mPackageName + " (pid " + mPid + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a particular client of the media router.
|
||||
* The contents of this object is guarded by mLock.
|
||||
@@ -676,6 +868,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
public final int mPid;
|
||||
public final String mPackageName;
|
||||
public final boolean mTrusted;
|
||||
public List<String> mControlCategories;
|
||||
|
||||
public int mRouteTypes;
|
||||
public boolean mActiveScan;
|
||||
@@ -726,7 +919,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
*/
|
||||
final class UserRecord {
|
||||
public final int mUserId;
|
||||
public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
|
||||
public final ArrayList<ClientRecord> mClientRecords = new ArrayList<>();
|
||||
public final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
|
||||
public final UserHandler mHandler;
|
||||
public MediaRouterClientState mRouterState;
|
||||
|
||||
@@ -781,7 +975,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
*/
|
||||
static final class UserHandler extends Handler
|
||||
implements RemoteDisplayProviderWatcher.Callback,
|
||||
RemoteDisplayProviderProxy.Callback {
|
||||
RemoteDisplayProviderProxy.Callback,
|
||||
MediaRoute2ProviderWatcher.Callback,
|
||||
MediaRoute2ProviderProxy.Callback {
|
||||
public static final int MSG_START = 1;
|
||||
public static final int MSG_STOP = 2;
|
||||
public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
|
||||
@@ -792,6 +988,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
private static final int MSG_UPDATE_CLIENT_STATE = 8;
|
||||
private static final int MSG_CONNECTION_TIMED_OUT = 9;
|
||||
|
||||
private static final int MSG_SELECT_REMOTE_ROUTE = 10;
|
||||
private static final int MSG_UPDATE_CLIENT_USAGE = 11;
|
||||
|
||||
private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
|
||||
private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
|
||||
private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
|
||||
@@ -807,11 +1006,17 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
private final MediaRouterService mService;
|
||||
private final UserRecord mUserRecord;
|
||||
private final RemoteDisplayProviderWatcher mWatcher;
|
||||
private final MediaRoute2ProviderWatcher mMediaWatcher;
|
||||
|
||||
private final ArrayList<ProviderRecord> mProviderRecords =
|
||||
new ArrayList<ProviderRecord>();
|
||||
private final ArrayList<IMediaRouterClient> mTempClients =
|
||||
new ArrayList<IMediaRouterClient>();
|
||||
|
||||
private final ArrayList<MediaRoute2ProviderProxy> mMediaProviders =
|
||||
new ArrayList<>();
|
||||
private final ArrayList<IMediaRouter2ManagerClient> mTempManagers = new ArrayList<>();
|
||||
|
||||
private boolean mRunning;
|
||||
private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
|
||||
private RouteRecord mSelectedRouteRecord;
|
||||
@@ -826,6 +1031,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
mUserRecord = userRecord;
|
||||
mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
|
||||
this, mUserRecord.mUserId);
|
||||
mMediaWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
|
||||
this, mUserRecord.mUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -867,6 +1074,15 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
connectionTimedOut();
|
||||
break;
|
||||
}
|
||||
case MSG_SELECT_REMOTE_ROUTE: {
|
||||
Pair<Integer, String> obj = (Pair<Integer, String>) msg.obj;
|
||||
selectRemoteRoute(obj.first, obj.second);
|
||||
break;
|
||||
}
|
||||
case MSG_UPDATE_CLIENT_USAGE: {
|
||||
updateClientUsage((ClientRecord) msg.obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,6 +1114,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
if (!mRunning) {
|
||||
mRunning = true;
|
||||
mWatcher.start(); // also starts all providers
|
||||
mMediaWatcher.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,6 +1123,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
mRunning = false;
|
||||
unselectSelectedRoute();
|
||||
mWatcher.stop(); // also stops all providers
|
||||
mMediaWatcher.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,6 +1255,26 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProvider(MediaRoute2ProviderProxy provider) {
|
||||
provider.setCallback(this);
|
||||
mMediaProviders.add(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProvider(MediaRoute2ProviderProxy provider) {
|
||||
mMediaProviders.remove(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStateChanged(MediaRoute2ProviderProxy provider) {
|
||||
updateProvider(provider);
|
||||
}
|
||||
|
||||
private void updateProvider(MediaRoute2ProviderProxy provider) {
|
||||
scheduleUpdateClientState();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called whenever the state of the selected route may have changed.
|
||||
* It checks the state and updates timeouts or unselects the route as appropriate.
|
||||
@@ -1147,6 +1385,17 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
unselectSelectedRoute();
|
||||
}
|
||||
|
||||
private void selectRemoteRoute(int uid, String routeId) {
|
||||
if (routeId != null) {
|
||||
final int providerCount = mMediaProviders.size();
|
||||
|
||||
//TODO: should find proper provider (currently assumes a single provider)
|
||||
for (int i = 0; i < providerCount; ++i) {
|
||||
mMediaProviders.get(i).setSelectedRoute(uid, routeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleUpdateClientState() {
|
||||
if (!mClientStateUpdateScheduled) {
|
||||
mClientStateUpdateScheduled = true;
|
||||
@@ -1164,6 +1413,15 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
mProviderRecords.get(i).appendClientState(routerState);
|
||||
}
|
||||
|
||||
//TODO: send provider info
|
||||
int selectedUid = 0;
|
||||
String selectedRouteId = null;
|
||||
final int mediaCount = mMediaProviders.size();
|
||||
for (int i = 0; i < mediaCount; i++) {
|
||||
selectedUid = mMediaProviders.get(i).mSelectedUid;
|
||||
selectedRouteId = mMediaProviders.get(i).mSelectedRouteId;
|
||||
}
|
||||
|
||||
try {
|
||||
synchronized (mService.mLock) {
|
||||
// Update the UserRecord.
|
||||
@@ -1174,6 +1432,11 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
for (int i = 0; i < count; i++) {
|
||||
mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
|
||||
}
|
||||
|
||||
final int count2 = mUserRecord.mManagerRecords.size();
|
||||
for (int i = 0; i < count2; i++) {
|
||||
mTempManagers.add(mUserRecord.mManagerRecords.get(i).mClient);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify all clients (outside of the lock).
|
||||
@@ -1185,9 +1448,39 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
Slog.w(TAG, "Failed to call onStateChanged. Client probably died.");
|
||||
}
|
||||
}
|
||||
//TODO: Call proper callbacks when provider descriptor is implemented.
|
||||
final int count2 = mTempManagers.size();
|
||||
for (int i = 0; i < count2; i++) {
|
||||
try {
|
||||
mTempManagers.get(i).onRouteSelected(selectedUid, selectedRouteId);
|
||||
} catch (RemoteException ex) {
|
||||
Slog.w(TAG, "Failed to call onStateChanged. Manager probably died.", ex);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Clear the list in preparation for the next time.
|
||||
mTempClients.clear();
|
||||
mTempManagers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateClientUsage(ClientRecord clientRecord) {
|
||||
List<IMediaRouter2ManagerClient> managers = new ArrayList<>();
|
||||
synchronized (mService.mLock) {
|
||||
final int count = mUserRecord.mManagerRecords.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
managers.add(mUserRecord.mManagerRecords.get(i).mClient);
|
||||
}
|
||||
}
|
||||
final int count = managers.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
try {
|
||||
managers.get(i).onControlCategoriesChanged(clientRecord.mUid,
|
||||
clientRecord.mControlCategories);
|
||||
} catch (RemoteException ex) {
|
||||
Slog.w(TAG, "Failed to call onControlCategoriesChanged. "
|
||||
+ "Manager probably died.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1574,4 +1867,5 @@ public final class MediaRouterService extends IMediaRouterService.Stub
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user