Loading media/java/android/media/IMediaRouter2Manager.aidl +3 −1 Original line number Original line Diff line number Diff line Loading @@ -25,5 +25,7 @@ import android.media.MediaRoute2Info; oneway interface IMediaRouter2Manager { oneway interface IMediaRouter2Manager { void notifyRouteSelected(String packageName, in MediaRoute2Info route); void notifyRouteSelected(String packageName, in MediaRoute2Info route); void notifyControlCategoriesChanged(String packageName, in List<String> categories); void notifyControlCategoriesChanged(String packageName, in List<String> categories); void notifyProviderInfosUpdated(in List<MediaRoute2ProviderInfo> providers); void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); } } media/java/android/media/MediaRouter2.java +5 −1 Original line number Original line Diff line number Diff line Loading @@ -95,6 +95,10 @@ public class MediaRouter2 { private final String mPackageName; private final String mPackageName; private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); //TODO: Use a lock for this to cover the below use case // mRouter.setControlCategories(...); // routes = mRouter.getRoutes(); // The current implementation returns empty list private volatile List<String> mControlCategories = Collections.emptyList(); private volatile List<String> mControlCategories = Collections.emptyList(); private MediaRoute2Info mSelectedRoute; private MediaRoute2Info mSelectedRoute; Loading Loading @@ -202,6 +206,7 @@ public class MediaRouter2 { } catch (RemoteException ex) { } catch (RemoteException ex) { Log.e(TAG, "Unable to unregister media router.", ex); Log.e(TAG, "Unable to unregister media router.", ex); } } //TODO: Clean up mRoutes. (onHandler?) mClient = null; mClient = null; } } } } Loading @@ -222,7 +227,6 @@ public class MediaRouter2 { MediaRouter2.this, new ArrayList<>(controlCategories))); MediaRouter2.this, new ArrayList<>(controlCategories))); } } /** /** * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently * known to the media router. * known to the media router. Loading media/java/android/media/MediaRouter2Manager.java +93 −120 Original line number Original line Diff line number Diff line Loading @@ -25,18 +25,16 @@ import android.content.Context; import android.os.Handler; import android.os.Handler; import android.os.RemoteException; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager; import android.text.TextUtils; import android.util.Log; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Collections; import java.util.HashSet; import java.util.HashMap; import java.util.List; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList; Loading @@ -59,13 +57,11 @@ public class MediaRouter2Manager { private Client mClient; private Client mClient; private final IMediaRouterService mMediaRouterService; private final IMediaRouterService mMediaRouterService; final Handler mHandler; final Handler mHandler; final List<CallbackRecord> mCallbacks = new CopyOnWriteArrayList<>(); final List<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>(); @SuppressWarnings("WeakerAccess") /* synthetic access */ private final Object mRoutesLock = new Object(); @NonNull @GuardedBy("mRoutesLock") List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList(); private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @NonNull List<MediaRoute2Info> mRoutes = Collections.emptyList(); @NonNull @NonNull final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>(); final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>(); Loading Loading @@ -104,13 +100,13 @@ public class MediaRouter2Manager { Objects.requireNonNull(callback, "callback must not be null"); Objects.requireNonNull(callback, "callback must not be null"); CallbackRecord callbackRecord; CallbackRecord callbackRecord; synchronized (mCallbacks) { synchronized (mCallbackRecords) { if (findCallbackRecordIndexLocked(callback) >= 0) { if (findCallbackRecordIndexLocked(callback) >= 0) { Log.w(TAG, "Ignoring to add the same callback twice."); Log.w(TAG, "Ignoring to add the same callback twice."); return; return; } } callbackRecord = new CallbackRecord(executor, callback); callbackRecord = new CallbackRecord(executor, callback); mCallbacks.add(callbackRecord); mCallbackRecords.add(callbackRecord); } } synchronized (sLock) { synchronized (sLock) { Loading @@ -136,32 +132,32 @@ public class MediaRouter2Manager { public void unregisterCallback(@NonNull Callback callback) { public void unregisterCallback(@NonNull Callback callback) { Objects.requireNonNull(callback, "callback must not be null"); Objects.requireNonNull(callback, "callback must not be null"); synchronized (mCallbacks) { synchronized (mCallbackRecords) { final int index = findCallbackRecordIndexLocked(callback); final int index = findCallbackRecordIndexLocked(callback); if (index < 0) { if (index < 0) { Log.w(TAG, "Ignore removing unknown callback. " + callback); Log.w(TAG, "Ignore removing unknown callback. " + callback); return; return; } } mCallbacks.remove(index); mCallbackRecords.remove(index); synchronized (sLock) { synchronized (sLock) { if (mCallbacks.size() == 0 && mClient != null) { if (mCallbackRecords.size() == 0 && mClient != null) { try { try { mMediaRouterService.unregisterManager(mClient); mMediaRouterService.unregisterManager(mClient); } catch (RemoteException ex) { } catch (RemoteException ex) { Log.e(TAG, "Unable to unregister media router manager", ex); Log.e(TAG, "Unable to unregister media router manager", ex); } } mClient.notifyProviderInfosUpdated(Collections.emptyList()); //TODO: clear mRoutes? mClient = null; mClient = null; } } } } } } } } @GuardedBy("mCallbacks") @GuardedBy("mCallbackRecords") private int findCallbackRecordIndexLocked(Callback callback) { private int findCallbackRecordIndexLocked(Callback callback) { final int count = mCallbacks.size(); final int count = mCallbackRecords.size(); for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) { if (mCallbacks.get(i).mCallback == callback) { if (mCallbackRecords.get(i).mCallback == callback) { return i; return i; } } } } Loading @@ -184,11 +180,14 @@ public class MediaRouter2Manager { return Collections.emptyList(); return Collections.emptyList(); } } List<MediaRoute2Info> routes = new ArrayList<>(); List<MediaRoute2Info> routes = new ArrayList<>(); for (MediaRoute2Info route : mRoutes) { synchronized (mRoutesLock) { for (MediaRoute2Info route : mRoutes.values()) { if (route.supportsControlCategory(controlCategories)) { if (route.supportsControlCategory(controlCategories)) { routes.add(route); routes.add(route); } } } } } //TODO: Should we cache this? return routes; return routes; } } Loading Loading @@ -282,136 +281,96 @@ public class MediaRouter2Manager { } } } } int findProviderIndex(MediaRoute2ProviderInfo provider) { void addRoutesOnHandler(List<MediaRoute2Info> routes) { final int count = mProviders.size(); synchronized (mRoutesLock) { for (int i = 0; i < count; i++) { for (MediaRoute2Info route : routes) { if (TextUtils.equals(mProviders.get(i).getUniqueId(), provider.getUniqueId())) { mRoutes.put(route.getUniqueId(), route); return i; } } } } return -1; if (routes.size() > 0) { notifyRoutesAdded(routes); } } void updateProvider(@NonNull MediaRoute2ProviderInfo provider) { if (provider == null || !provider.isValid()) { Log.w(TAG, "Ignoring invalid provider : " + provider); return; } } final Collection<MediaRoute2Info> routes = provider.getRoutes(); void removeRoutesOnHandler(List<MediaRoute2Info> routes) { synchronized (mRoutesLock) { final int index = findProviderIndex(provider); for (MediaRoute2Info route : routes) { if (index >= 0) { mRoutes.remove(route.getUniqueId()); final MediaRoute2ProviderInfo prevProvider = mProviders.get(index); final Set<String> updatedRouteIds = new HashSet<>(); for (MediaRoute2Info routeInfo : routes) { if (!routeInfo.isValid()) { Log.w(TAG, "Ignoring invalid route : " + routeInfo); continue; } } final MediaRoute2Info prevRoute = prevProvider.getRoute(routeInfo.getId()); if (prevRoute == null) { notifyRouteAdded(routeInfo); } else { if (!Objects.equals(prevRoute, routeInfo)) { notifyRouteChanged(routeInfo); } } updatedRouteIds.add(routeInfo.getId()); if (routes.size() > 0) { notifyRoutesRemoved(routes); } } } } final Collection<MediaRoute2Info> prevRoutes = prevProvider.getRoutes(); for (MediaRoute2Info prevRoute : prevRoutes) { void changeRoutesOnHandler(List<MediaRoute2Info> routes) { if (!updatedRouteIds.contains(prevRoute.getId())) { synchronized (mRoutesLock) { notifyRouteRemoved(prevRoute); for (MediaRoute2Info route : routes) { mRoutes.put(route.getUniqueId(), route); } } } } } else { if (routes.size() > 0) { for (MediaRoute2Info routeInfo: routes) { notifyRoutesChanged(routes); notifyRouteAdded(routeInfo); } } } void notifyRouteAdded(MediaRoute2Info routeInfo) { for (CallbackRecord record : mCallbacks) { record.mExecutor.execute( () -> record.mCallback.onRouteAdded(routeInfo)); } } } } void notifyRouteChanged(MediaRoute2Info routeInfo) { private void notifyRoutesAdded(List<MediaRoute2Info> routes) { for (CallbackRecord record : mCallbacks) { for (CallbackRecord record: mCallbackRecords) { record.mExecutor.execute( record.mExecutor.execute( () -> record.mCallback.onRouteChanged(routeInfo)); () -> record.mCallback.onRoutesAdded(routes)); } } } } void notifyRouteRemoved(MediaRoute2Info routeInfo) { private void notifyRoutesRemoved(List<MediaRoute2Info> routes) { for (CallbackRecord record : mCallbacks) { for (CallbackRecord record: mCallbackRecords) { record.mExecutor.execute( record.mExecutor.execute( () -> record.mCallback.onRouteRemoved(routeInfo)); () -> record.mCallback.onRoutesRemoved(routes)); } } } } void notifyRouteListChanged() { private void notifyRoutesChanged(List<MediaRoute2Info> routes) { for (CallbackRecord record: mCallbacks) { for (CallbackRecord record: mCallbackRecords) { record.mExecutor.execute( record.mExecutor.execute( () -> record.mCallback.onRoutesChanged(mRoutes)); () -> record.mCallback.onRoutesChanged(routes)); } } } } void notifyProviderInfosUpdated(List<MediaRoute2ProviderInfo> providers) { if (providers == null) { Log.w(TAG, "Providers info is null."); return; } ArrayList<MediaRoute2Info> routes = new ArrayList<>(); for (MediaRoute2ProviderInfo provider : providers) { updateProvider(provider); //TODO: Should we do this in updateProvider()? routes.addAll(provider.getRoutes()); } //TODO: Call notifyRouteRemoved for the routes of the removed providers. //TODO: Filter invalid providers and invalid routes. mProviders = providers; mRoutes = routes; //TODO: Call this when only the list is modified. notifyRouteListChanged(); } void notifyRouteSelected(String packageName, MediaRoute2Info route) { void notifyRouteSelected(String packageName, MediaRoute2Info route) { for (CallbackRecord record : mCallbacks) { for (CallbackRecord record : mCallbackRecords) { record.mExecutor.execute(() -> record.mCallback.onRouteSelected(packageName, route)); record.mExecutor.execute(() -> record.mCallback.onRouteSelected(packageName, route)); } } } } void updateControlCategories(String packageName, List<String> categories) { void updateControlCategories(String packageName, List<String> categories) { mControlCategoryMap.put(packageName, categories); mControlCategoryMap.put(packageName, categories); for (CallbackRecord record : mCallbackRecords) { record.mExecutor.execute( () -> record.mCallback.onControlCategoriesChanged(packageName)); } } } /** /** * Interface for receiving events about media routing changes. * Interface for receiving events about media routing changes. */ */ public static class Callback { public static class Callback { /** /** * Called when a route is added. * Called when routes are added. * @param routes the list of routes that have been added. It's never empty. */ */ public void onRouteAdded(@NonNull MediaRoute2Info routeInfo) {} public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {} /** /** * Called when a route is changed. * Called when routes are removed. * @param routes the list of routes that have been removed. It's never empty. */ */ public void onRouteChanged(@NonNull MediaRoute2Info routeInfo) {} public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {} /** /** * Called when a route is removed. * Called when routes are changed. * @param routes the list of routes that have been changed. It's never empty. */ */ public void onRouteRemoved(@NonNull MediaRoute2Info routeInfo) {} public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {} /** /** * Called when a route is selected for an application. * Called when a route is selected for an application. Loading @@ -422,13 +381,13 @@ public class MediaRouter2Manager { */ */ public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {} public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {} /** /** * Called when the list of routes is changed. * Called when the control categories of an app is changed. * A client may refresh available routes for each application. * * @param packageName the package name of the application */ */ public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {} public void onControlCategoriesChanged(@NonNull String packageName) {} //TODO: add onControlCategoriesChanged to notify available routes are changed } } final class CallbackRecord { final class CallbackRecord { Loading @@ -441,10 +400,12 @@ public class MediaRouter2Manager { } } void notifyRoutes() { void notifyRoutes() { mExecutor.execute(() -> mCallback.onRoutesChanged(mRoutes)); List<MediaRoute2Info> routes; for (MediaRoute2Info routeInfo : mRoutes) { synchronized (mRoutesLock) { mExecutor.execute( routes = new ArrayList<>(mRoutes.values()); () -> mCallback.onRouteAdded(routeInfo)); } if (routes.size() > 0) { mExecutor.execute(() -> mCallback.onRoutesAdded(routes)); } } } } } } Loading @@ -463,9 +424,21 @@ public class MediaRouter2Manager { } } @Override @Override public void notifyProviderInfosUpdated(List<MediaRoute2ProviderInfo> info) { public void notifyRoutesAdded(List<MediaRoute2Info> routes) { mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyProviderInfosUpdated, mHandler.sendMessage(obtainMessage(MediaRouter2Manager::addRoutesOnHandler, MediaRouter2Manager.this, info)); MediaRouter2Manager.this, routes)); } @Override public void notifyRoutesRemoved(List<MediaRoute2Info> routes) { mHandler.sendMessage(obtainMessage(MediaRouter2Manager::removeRoutesOnHandler, MediaRouter2Manager.this, routes)); } @Override public void notifyRoutesChanged(List<MediaRoute2Info> routes) { mHandler.sendMessage(obtainMessage(MediaRouter2Manager::changeRoutesOnHandler, MediaRouter2Manager.this, routes)); } } } } } } media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +114 −104 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +62 −32 Original line number Original line Diff line number Diff line Loading @@ -469,7 +469,7 @@ class MediaRouter2ServiceImpl { mAllManagerRecords.put(binder, managerRecord); mAllManagerRecords.put(binder, managerRecord); userRecord.mHandler.sendMessage( userRecord.mHandler.sendMessage( obtainMessage(UserHandler::notifyProviderInfosUpdatedToManager, obtainMessage(UserHandler::notifyRoutesToManager, userRecord.mHandler, manager)); userRecord.mHandler, manager)); for (ClientRecord clientRecord : userRecord.mClientRecords) { for (ClientRecord clientRecord : userRecord.mClientRecords) { Loading Loading @@ -808,17 +808,20 @@ class MediaRouter2ServiceImpl { } } List<IMediaRouter2Client> clients = getClients(); List<IMediaRouter2Client> clients = getClients(); List<IMediaRouter2Manager> managers = getManagers(); if (addedRoutes.size() > 0) { if (addedRoutes.size() > 0) { notifyRoutesAddedToClients(clients, addedRoutes); notifyRoutesAddedToClients(clients, addedRoutes); notifyRoutesAddedToManagers(managers, addedRoutes); } } if (removedRoutes.size() > 0) { if (removedRoutes.size() > 0) { notifyRoutesRemovedToClients(clients, removedRoutes); notifyRoutesRemovedToClients(clients, removedRoutes); notifyRoutesRemovedToManagers(managers, removedRoutes); } } if (changedRoutes.size() > 0) { if (changedRoutes.size() > 0) { notifyRoutesChangedToClients(clients, changedRoutes); notifyRoutesChangedToClients(clients, changedRoutes); notifyRoutesChangedToManagers(managers, changedRoutes); } } } } scheduleUpdateProviderInfos(); } } private int getProviderInfoIndex(String providerId) { private int getProviderInfoIndex(String providerId) { Loading Loading @@ -874,47 +877,34 @@ class MediaRouter2ServiceImpl { } } } } private void scheduleUpdateProviderInfos() { private List<IMediaRouter2Client> getClients() { if (!mProviderInfosUpdateScheduled) { final List<IMediaRouter2Client> clients = new ArrayList<>(); mProviderInfosUpdateScheduled = true; sendMessage(PooledLambda.obtainMessage(UserHandler::updateProviderInfos, this)); } } //TODO: should be replaced into notifyRoutes...ToManagers private void updateProviderInfos() { mProviderInfosUpdateScheduled = false; MediaRouter2ServiceImpl service = mServiceRef.get(); MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { if (service == null) { return; return clients; } } final List<IMediaRouter2Manager> managers = new ArrayList<>(); synchronized (service.mLock) { synchronized (service.mLock) { for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { for (ClientRecord clientRecord : mUserRecord.mClientRecords) { managers.add(managerRecord.mManager); if (clientRecord instanceof Client2Record) { clients.add(((Client2Record) clientRecord).mClient); } } } } for (IMediaRouter2Manager manager : managers) { notifyProviderInfosUpdatedToManager(manager); } } return clients; } } private List<IMediaRouter2Client> getClients() { private List<IMediaRouter2Manager> getManagers() { final List<IMediaRouter2Client> clients = new ArrayList<>(); final List<IMediaRouter2Manager> managers = new ArrayList<>(); MediaRouter2ServiceImpl service = mServiceRef.get(); MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { if (service == null) { return clients; return managers; } } synchronized (service.mLock) { synchronized (service.mLock) { for (ClientRecord clientRecord : mUserRecord.mClientRecords) { for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { if (clientRecord instanceof Client2Record) { managers.add(managerRecord.mManager); clients.add(((Client2Record) clientRecord).mClient); } } } } } return managers; return clients; } } private void notifyRoutesToClient(IMediaRouter2Client client) { private void notifyRoutesToClient(IMediaRouter2Client client) { Loading Loading @@ -965,11 +955,51 @@ class MediaRouter2ServiceImpl { } } } } private void notifyProviderInfosUpdatedToManager(IMediaRouter2Manager manager) { private void notifyRoutesToManager(IMediaRouter2Manager manager) { List<MediaRoute2Info> routes = new ArrayList<>(); for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) { routes.addAll(providerInfo.getRoutes()); } if (routes.size() == 0) { return; } try { try { manager.notifyProviderInfosUpdated(mProviderInfos); manager.notifyRoutesAdded(routes); } catch (RemoteException ex) { } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify provider infos updated. Manager probably died."); Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex); } } private void notifyRoutesAddedToManagers(List<IMediaRouter2Manager> managers, List<MediaRoute2Info> routes) { for (IMediaRouter2Manager manager : managers) { try { manager.notifyRoutesAdded(routes); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex); } } } private void notifyRoutesRemovedToManagers(List<IMediaRouter2Manager> managers, List<MediaRoute2Info> routes) { for (IMediaRouter2Manager manager : managers) { try { manager.notifyRoutesRemoved(routes); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex); } } } private void notifyRoutesChangedToManagers(List<IMediaRouter2Manager> managers, List<MediaRoute2Info> routes) { for (IMediaRouter2Manager manager : managers) { try { manager.notifyRoutesChanged(routes); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex); } } } } } Loading Loading
media/java/android/media/IMediaRouter2Manager.aidl +3 −1 Original line number Original line Diff line number Diff line Loading @@ -25,5 +25,7 @@ import android.media.MediaRoute2Info; oneway interface IMediaRouter2Manager { oneway interface IMediaRouter2Manager { void notifyRouteSelected(String packageName, in MediaRoute2Info route); void notifyRouteSelected(String packageName, in MediaRoute2Info route); void notifyControlCategoriesChanged(String packageName, in List<String> categories); void notifyControlCategoriesChanged(String packageName, in List<String> categories); void notifyProviderInfosUpdated(in List<MediaRoute2ProviderInfo> providers); void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); } }
media/java/android/media/MediaRouter2.java +5 −1 Original line number Original line Diff line number Diff line Loading @@ -95,6 +95,10 @@ public class MediaRouter2 { private final String mPackageName; private final String mPackageName; private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); //TODO: Use a lock for this to cover the below use case // mRouter.setControlCategories(...); // routes = mRouter.getRoutes(); // The current implementation returns empty list private volatile List<String> mControlCategories = Collections.emptyList(); private volatile List<String> mControlCategories = Collections.emptyList(); private MediaRoute2Info mSelectedRoute; private MediaRoute2Info mSelectedRoute; Loading Loading @@ -202,6 +206,7 @@ public class MediaRouter2 { } catch (RemoteException ex) { } catch (RemoteException ex) { Log.e(TAG, "Unable to unregister media router.", ex); Log.e(TAG, "Unable to unregister media router.", ex); } } //TODO: Clean up mRoutes. (onHandler?) mClient = null; mClient = null; } } } } Loading @@ -222,7 +227,6 @@ public class MediaRouter2 { MediaRouter2.this, new ArrayList<>(controlCategories))); MediaRouter2.this, new ArrayList<>(controlCategories))); } } /** /** * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently * known to the media router. * known to the media router. Loading
media/java/android/media/MediaRouter2Manager.java +93 −120 Original line number Original line Diff line number Diff line Loading @@ -25,18 +25,16 @@ import android.content.Context; import android.os.Handler; import android.os.Handler; import android.os.RemoteException; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager; import android.text.TextUtils; import android.util.Log; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Collections; import java.util.HashSet; import java.util.HashMap; import java.util.List; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList; Loading @@ -59,13 +57,11 @@ public class MediaRouter2Manager { private Client mClient; private Client mClient; private final IMediaRouterService mMediaRouterService; private final IMediaRouterService mMediaRouterService; final Handler mHandler; final Handler mHandler; final List<CallbackRecord> mCallbacks = new CopyOnWriteArrayList<>(); final List<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>(); @SuppressWarnings("WeakerAccess") /* synthetic access */ private final Object mRoutesLock = new Object(); @NonNull @GuardedBy("mRoutesLock") List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList(); private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @NonNull List<MediaRoute2Info> mRoutes = Collections.emptyList(); @NonNull @NonNull final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>(); final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>(); Loading Loading @@ -104,13 +100,13 @@ public class MediaRouter2Manager { Objects.requireNonNull(callback, "callback must not be null"); Objects.requireNonNull(callback, "callback must not be null"); CallbackRecord callbackRecord; CallbackRecord callbackRecord; synchronized (mCallbacks) { synchronized (mCallbackRecords) { if (findCallbackRecordIndexLocked(callback) >= 0) { if (findCallbackRecordIndexLocked(callback) >= 0) { Log.w(TAG, "Ignoring to add the same callback twice."); Log.w(TAG, "Ignoring to add the same callback twice."); return; return; } } callbackRecord = new CallbackRecord(executor, callback); callbackRecord = new CallbackRecord(executor, callback); mCallbacks.add(callbackRecord); mCallbackRecords.add(callbackRecord); } } synchronized (sLock) { synchronized (sLock) { Loading @@ -136,32 +132,32 @@ public class MediaRouter2Manager { public void unregisterCallback(@NonNull Callback callback) { public void unregisterCallback(@NonNull Callback callback) { Objects.requireNonNull(callback, "callback must not be null"); Objects.requireNonNull(callback, "callback must not be null"); synchronized (mCallbacks) { synchronized (mCallbackRecords) { final int index = findCallbackRecordIndexLocked(callback); final int index = findCallbackRecordIndexLocked(callback); if (index < 0) { if (index < 0) { Log.w(TAG, "Ignore removing unknown callback. " + callback); Log.w(TAG, "Ignore removing unknown callback. " + callback); return; return; } } mCallbacks.remove(index); mCallbackRecords.remove(index); synchronized (sLock) { synchronized (sLock) { if (mCallbacks.size() == 0 && mClient != null) { if (mCallbackRecords.size() == 0 && mClient != null) { try { try { mMediaRouterService.unregisterManager(mClient); mMediaRouterService.unregisterManager(mClient); } catch (RemoteException ex) { } catch (RemoteException ex) { Log.e(TAG, "Unable to unregister media router manager", ex); Log.e(TAG, "Unable to unregister media router manager", ex); } } mClient.notifyProviderInfosUpdated(Collections.emptyList()); //TODO: clear mRoutes? mClient = null; mClient = null; } } } } } } } } @GuardedBy("mCallbacks") @GuardedBy("mCallbackRecords") private int findCallbackRecordIndexLocked(Callback callback) { private int findCallbackRecordIndexLocked(Callback callback) { final int count = mCallbacks.size(); final int count = mCallbackRecords.size(); for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) { if (mCallbacks.get(i).mCallback == callback) { if (mCallbackRecords.get(i).mCallback == callback) { return i; return i; } } } } Loading @@ -184,11 +180,14 @@ public class MediaRouter2Manager { return Collections.emptyList(); return Collections.emptyList(); } } List<MediaRoute2Info> routes = new ArrayList<>(); List<MediaRoute2Info> routes = new ArrayList<>(); for (MediaRoute2Info route : mRoutes) { synchronized (mRoutesLock) { for (MediaRoute2Info route : mRoutes.values()) { if (route.supportsControlCategory(controlCategories)) { if (route.supportsControlCategory(controlCategories)) { routes.add(route); routes.add(route); } } } } } //TODO: Should we cache this? return routes; return routes; } } Loading Loading @@ -282,136 +281,96 @@ public class MediaRouter2Manager { } } } } int findProviderIndex(MediaRoute2ProviderInfo provider) { void addRoutesOnHandler(List<MediaRoute2Info> routes) { final int count = mProviders.size(); synchronized (mRoutesLock) { for (int i = 0; i < count; i++) { for (MediaRoute2Info route : routes) { if (TextUtils.equals(mProviders.get(i).getUniqueId(), provider.getUniqueId())) { mRoutes.put(route.getUniqueId(), route); return i; } } } } return -1; if (routes.size() > 0) { notifyRoutesAdded(routes); } } void updateProvider(@NonNull MediaRoute2ProviderInfo provider) { if (provider == null || !provider.isValid()) { Log.w(TAG, "Ignoring invalid provider : " + provider); return; } } final Collection<MediaRoute2Info> routes = provider.getRoutes(); void removeRoutesOnHandler(List<MediaRoute2Info> routes) { synchronized (mRoutesLock) { final int index = findProviderIndex(provider); for (MediaRoute2Info route : routes) { if (index >= 0) { mRoutes.remove(route.getUniqueId()); final MediaRoute2ProviderInfo prevProvider = mProviders.get(index); final Set<String> updatedRouteIds = new HashSet<>(); for (MediaRoute2Info routeInfo : routes) { if (!routeInfo.isValid()) { Log.w(TAG, "Ignoring invalid route : " + routeInfo); continue; } } final MediaRoute2Info prevRoute = prevProvider.getRoute(routeInfo.getId()); if (prevRoute == null) { notifyRouteAdded(routeInfo); } else { if (!Objects.equals(prevRoute, routeInfo)) { notifyRouteChanged(routeInfo); } } updatedRouteIds.add(routeInfo.getId()); if (routes.size() > 0) { notifyRoutesRemoved(routes); } } } } final Collection<MediaRoute2Info> prevRoutes = prevProvider.getRoutes(); for (MediaRoute2Info prevRoute : prevRoutes) { void changeRoutesOnHandler(List<MediaRoute2Info> routes) { if (!updatedRouteIds.contains(prevRoute.getId())) { synchronized (mRoutesLock) { notifyRouteRemoved(prevRoute); for (MediaRoute2Info route : routes) { mRoutes.put(route.getUniqueId(), route); } } } } } else { if (routes.size() > 0) { for (MediaRoute2Info routeInfo: routes) { notifyRoutesChanged(routes); notifyRouteAdded(routeInfo); } } } void notifyRouteAdded(MediaRoute2Info routeInfo) { for (CallbackRecord record : mCallbacks) { record.mExecutor.execute( () -> record.mCallback.onRouteAdded(routeInfo)); } } } } void notifyRouteChanged(MediaRoute2Info routeInfo) { private void notifyRoutesAdded(List<MediaRoute2Info> routes) { for (CallbackRecord record : mCallbacks) { for (CallbackRecord record: mCallbackRecords) { record.mExecutor.execute( record.mExecutor.execute( () -> record.mCallback.onRouteChanged(routeInfo)); () -> record.mCallback.onRoutesAdded(routes)); } } } } void notifyRouteRemoved(MediaRoute2Info routeInfo) { private void notifyRoutesRemoved(List<MediaRoute2Info> routes) { for (CallbackRecord record : mCallbacks) { for (CallbackRecord record: mCallbackRecords) { record.mExecutor.execute( record.mExecutor.execute( () -> record.mCallback.onRouteRemoved(routeInfo)); () -> record.mCallback.onRoutesRemoved(routes)); } } } } void notifyRouteListChanged() { private void notifyRoutesChanged(List<MediaRoute2Info> routes) { for (CallbackRecord record: mCallbacks) { for (CallbackRecord record: mCallbackRecords) { record.mExecutor.execute( record.mExecutor.execute( () -> record.mCallback.onRoutesChanged(mRoutes)); () -> record.mCallback.onRoutesChanged(routes)); } } } } void notifyProviderInfosUpdated(List<MediaRoute2ProviderInfo> providers) { if (providers == null) { Log.w(TAG, "Providers info is null."); return; } ArrayList<MediaRoute2Info> routes = new ArrayList<>(); for (MediaRoute2ProviderInfo provider : providers) { updateProvider(provider); //TODO: Should we do this in updateProvider()? routes.addAll(provider.getRoutes()); } //TODO: Call notifyRouteRemoved for the routes of the removed providers. //TODO: Filter invalid providers and invalid routes. mProviders = providers; mRoutes = routes; //TODO: Call this when only the list is modified. notifyRouteListChanged(); } void notifyRouteSelected(String packageName, MediaRoute2Info route) { void notifyRouteSelected(String packageName, MediaRoute2Info route) { for (CallbackRecord record : mCallbacks) { for (CallbackRecord record : mCallbackRecords) { record.mExecutor.execute(() -> record.mCallback.onRouteSelected(packageName, route)); record.mExecutor.execute(() -> record.mCallback.onRouteSelected(packageName, route)); } } } } void updateControlCategories(String packageName, List<String> categories) { void updateControlCategories(String packageName, List<String> categories) { mControlCategoryMap.put(packageName, categories); mControlCategoryMap.put(packageName, categories); for (CallbackRecord record : mCallbackRecords) { record.mExecutor.execute( () -> record.mCallback.onControlCategoriesChanged(packageName)); } } } /** /** * Interface for receiving events about media routing changes. * Interface for receiving events about media routing changes. */ */ public static class Callback { public static class Callback { /** /** * Called when a route is added. * Called when routes are added. * @param routes the list of routes that have been added. It's never empty. */ */ public void onRouteAdded(@NonNull MediaRoute2Info routeInfo) {} public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {} /** /** * Called when a route is changed. * Called when routes are removed. * @param routes the list of routes that have been removed. It's never empty. */ */ public void onRouteChanged(@NonNull MediaRoute2Info routeInfo) {} public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {} /** /** * Called when a route is removed. * Called when routes are changed. * @param routes the list of routes that have been changed. It's never empty. */ */ public void onRouteRemoved(@NonNull MediaRoute2Info routeInfo) {} public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {} /** /** * Called when a route is selected for an application. * Called when a route is selected for an application. Loading @@ -422,13 +381,13 @@ public class MediaRouter2Manager { */ */ public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {} public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {} /** /** * Called when the list of routes is changed. * Called when the control categories of an app is changed. * A client may refresh available routes for each application. * * @param packageName the package name of the application */ */ public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {} public void onControlCategoriesChanged(@NonNull String packageName) {} //TODO: add onControlCategoriesChanged to notify available routes are changed } } final class CallbackRecord { final class CallbackRecord { Loading @@ -441,10 +400,12 @@ public class MediaRouter2Manager { } } void notifyRoutes() { void notifyRoutes() { mExecutor.execute(() -> mCallback.onRoutesChanged(mRoutes)); List<MediaRoute2Info> routes; for (MediaRoute2Info routeInfo : mRoutes) { synchronized (mRoutesLock) { mExecutor.execute( routes = new ArrayList<>(mRoutes.values()); () -> mCallback.onRouteAdded(routeInfo)); } if (routes.size() > 0) { mExecutor.execute(() -> mCallback.onRoutesAdded(routes)); } } } } } } Loading @@ -463,9 +424,21 @@ public class MediaRouter2Manager { } } @Override @Override public void notifyProviderInfosUpdated(List<MediaRoute2ProviderInfo> info) { public void notifyRoutesAdded(List<MediaRoute2Info> routes) { mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyProviderInfosUpdated, mHandler.sendMessage(obtainMessage(MediaRouter2Manager::addRoutesOnHandler, MediaRouter2Manager.this, info)); MediaRouter2Manager.this, routes)); } @Override public void notifyRoutesRemoved(List<MediaRoute2Info> routes) { mHandler.sendMessage(obtainMessage(MediaRouter2Manager::removeRoutesOnHandler, MediaRouter2Manager.this, routes)); } @Override public void notifyRoutesChanged(List<MediaRoute2Info> routes) { mHandler.sendMessage(obtainMessage(MediaRouter2Manager::changeRoutesOnHandler, MediaRouter2Manager.this, routes)); } } } } } }
media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +114 −104 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +62 −32 Original line number Original line Diff line number Diff line Loading @@ -469,7 +469,7 @@ class MediaRouter2ServiceImpl { mAllManagerRecords.put(binder, managerRecord); mAllManagerRecords.put(binder, managerRecord); userRecord.mHandler.sendMessage( userRecord.mHandler.sendMessage( obtainMessage(UserHandler::notifyProviderInfosUpdatedToManager, obtainMessage(UserHandler::notifyRoutesToManager, userRecord.mHandler, manager)); userRecord.mHandler, manager)); for (ClientRecord clientRecord : userRecord.mClientRecords) { for (ClientRecord clientRecord : userRecord.mClientRecords) { Loading Loading @@ -808,17 +808,20 @@ class MediaRouter2ServiceImpl { } } List<IMediaRouter2Client> clients = getClients(); List<IMediaRouter2Client> clients = getClients(); List<IMediaRouter2Manager> managers = getManagers(); if (addedRoutes.size() > 0) { if (addedRoutes.size() > 0) { notifyRoutesAddedToClients(clients, addedRoutes); notifyRoutesAddedToClients(clients, addedRoutes); notifyRoutesAddedToManagers(managers, addedRoutes); } } if (removedRoutes.size() > 0) { if (removedRoutes.size() > 0) { notifyRoutesRemovedToClients(clients, removedRoutes); notifyRoutesRemovedToClients(clients, removedRoutes); notifyRoutesRemovedToManagers(managers, removedRoutes); } } if (changedRoutes.size() > 0) { if (changedRoutes.size() > 0) { notifyRoutesChangedToClients(clients, changedRoutes); notifyRoutesChangedToClients(clients, changedRoutes); notifyRoutesChangedToManagers(managers, changedRoutes); } } } } scheduleUpdateProviderInfos(); } } private int getProviderInfoIndex(String providerId) { private int getProviderInfoIndex(String providerId) { Loading Loading @@ -874,47 +877,34 @@ class MediaRouter2ServiceImpl { } } } } private void scheduleUpdateProviderInfos() { private List<IMediaRouter2Client> getClients() { if (!mProviderInfosUpdateScheduled) { final List<IMediaRouter2Client> clients = new ArrayList<>(); mProviderInfosUpdateScheduled = true; sendMessage(PooledLambda.obtainMessage(UserHandler::updateProviderInfos, this)); } } //TODO: should be replaced into notifyRoutes...ToManagers private void updateProviderInfos() { mProviderInfosUpdateScheduled = false; MediaRouter2ServiceImpl service = mServiceRef.get(); MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { if (service == null) { return; return clients; } } final List<IMediaRouter2Manager> managers = new ArrayList<>(); synchronized (service.mLock) { synchronized (service.mLock) { for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { for (ClientRecord clientRecord : mUserRecord.mClientRecords) { managers.add(managerRecord.mManager); if (clientRecord instanceof Client2Record) { clients.add(((Client2Record) clientRecord).mClient); } } } } for (IMediaRouter2Manager manager : managers) { notifyProviderInfosUpdatedToManager(manager); } } return clients; } } private List<IMediaRouter2Client> getClients() { private List<IMediaRouter2Manager> getManagers() { final List<IMediaRouter2Client> clients = new ArrayList<>(); final List<IMediaRouter2Manager> managers = new ArrayList<>(); MediaRouter2ServiceImpl service = mServiceRef.get(); MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { if (service == null) { return clients; return managers; } } synchronized (service.mLock) { synchronized (service.mLock) { for (ClientRecord clientRecord : mUserRecord.mClientRecords) { for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { if (clientRecord instanceof Client2Record) { managers.add(managerRecord.mManager); clients.add(((Client2Record) clientRecord).mClient); } } } } } return managers; return clients; } } private void notifyRoutesToClient(IMediaRouter2Client client) { private void notifyRoutesToClient(IMediaRouter2Client client) { Loading Loading @@ -965,11 +955,51 @@ class MediaRouter2ServiceImpl { } } } } private void notifyProviderInfosUpdatedToManager(IMediaRouter2Manager manager) { private void notifyRoutesToManager(IMediaRouter2Manager manager) { List<MediaRoute2Info> routes = new ArrayList<>(); for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) { routes.addAll(providerInfo.getRoutes()); } if (routes.size() == 0) { return; } try { try { manager.notifyProviderInfosUpdated(mProviderInfos); manager.notifyRoutesAdded(routes); } catch (RemoteException ex) { } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify provider infos updated. Manager probably died."); Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex); } } private void notifyRoutesAddedToManagers(List<IMediaRouter2Manager> managers, List<MediaRoute2Info> routes) { for (IMediaRouter2Manager manager : managers) { try { manager.notifyRoutesAdded(routes); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex); } } } private void notifyRoutesRemovedToManagers(List<IMediaRouter2Manager> managers, List<MediaRoute2Info> routes) { for (IMediaRouter2Manager manager : managers) { try { manager.notifyRoutesRemoved(routes); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex); } } } private void notifyRoutesChangedToManagers(List<IMediaRouter2Manager> managers, List<MediaRoute2Info> routes) { for (IMediaRouter2Manager manager : managers) { try { manager.notifyRoutesChanged(routes); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex); } } } } } Loading