Loading media/java/android/media/IMediaRouterClient.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -21,4 +21,5 @@ package android.media; */ oneway interface IMediaRouterClient { void onStateChanged(); void onRestoreRoute(); } media/java/android/media/IMediaRouterService.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ interface IMediaRouterService { void unregisterClient(IMediaRouterClient client); MediaRouterClientState getState(IMediaRouterClient client); boolean isPlaybackActive(IMediaRouterClient client); void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan); void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); Loading media/java/android/media/MediaRouter.java +57 −18 Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ public class MediaRouter { RouteInfo mBluetoothA2dpRoute; RouteInfo mSelectedRoute; RouteInfo mSystemAudioRoute; final boolean mCanConfigureWifiDisplays; boolean mActivelyScanningWifiDisplays; Loading Loading @@ -149,6 +150,7 @@ public class MediaRouter { } addRouteStatic(mDefaultAudioVideo); mSystemAudioRoute = mDefaultAudioVideo; // This will select the active wifi display route if there is one. updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus()); Loading Loading @@ -197,8 +199,8 @@ public class MediaRouter { } else { name = com.android.internal.R.string.default_audio_route_name; } sStatic.mDefaultAudioVideo.mNameResId = name; dispatchRouteChanged(sStatic.mDefaultAudioVideo); mDefaultAudioVideo.mNameResId = name; dispatchRouteChanged(mDefaultAudioVideo); updated = true; } Loading @@ -207,22 +209,28 @@ public class MediaRouter { if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; if (mCurAudioRoutesInfo.bluetoothName != null) { if (sStatic.mBluetoothA2dpRoute == null) { final RouteInfo info = new RouteInfo(sStatic.mSystemCategory); if (mBluetoothA2dpRoute == null) { // BT connected final RouteInfo info = new RouteInfo(mSystemCategory); info.mName = mCurAudioRoutesInfo.bluetoothName; info.mDescription = sStatic.mResources.getText( info.mDescription = mResources.getText( com.android.internal.R.string.bluetooth_a2dp_audio_route_name); info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH; sStatic.mBluetoothA2dpRoute = info; addRouteStatic(sStatic.mBluetoothA2dpRoute); mBluetoothA2dpRoute = info; addRouteStatic(mBluetoothA2dpRoute); mSystemAudioRoute = mBluetoothA2dpRoute; selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false); } else { sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName; dispatchRouteChanged(sStatic.mBluetoothA2dpRoute); mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName; dispatchRouteChanged(mBluetoothA2dpRoute); } } else if (sStatic.mBluetoothA2dpRoute != null) { removeRouteStatic(sStatic.mBluetoothA2dpRoute); sStatic.mBluetoothA2dpRoute = null; } else if (mBluetoothA2dpRoute != null) { // BT disconnected removeRouteStatic(mBluetoothA2dpRoute); mBluetoothA2dpRoute = null; mSystemAudioRoute = mDefaultAudioVideo; selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false); } updated = true; } Loading @@ -230,11 +238,13 @@ public class MediaRouter { if (mBluetoothA2dpRoute != null) { final boolean a2dpEnabled = isBluetoothA2dpOn(); if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) { selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false); // A2DP off mSystemAudioRoute = mDefaultAudioVideo; updated = true; } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) && a2dpEnabled) { selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false); // A2DP on or BT connected mSystemAudioRoute = mBluetoothA2dpRoute; updated = true; } } Loading Loading @@ -471,7 +481,7 @@ public class MediaRouter { } RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) { RouteInfo route = new RouteInfo(sStatic.mSystemCategory); RouteInfo route = new RouteInfo(mSystemCategory); route.mGlobalRouteId = globalRoute.id; route.mName = globalRoute.name; route.mDescription = globalRoute.description; Loading Loading @@ -567,6 +577,17 @@ public class MediaRouter { return null; } boolean isPlaybackActive() { if (mClient != null) { try { return mMediaRouterService.isPlaybackActive(mClient); } catch (RemoteException ex) { Log.e(TAG, "Unable to retrieve playback active state.", ex); } } return false; } final class Client extends IMediaRouterClient.Stub { @Override public void onStateChanged() { Loading @@ -579,6 +600,19 @@ public class MediaRouter { } }); } @Override public void onRestoreRoute() { if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute) || mSelectedRoute == mSystemAudioRoute) { return; } try { sStatic.mAudioService.setBluetoothA2dpOn(mSelectedRoute == mBluetoothA2dpRoute); } catch (RemoteException e) { Log.e(TAG, "Error changing Bluetooth A2DP state", e); } } } } Loading Loading @@ -909,7 +943,12 @@ public class MediaRouter { Log.v(TAG, "Selecting route: " + route); assert(route != null); final RouteInfo oldRoute = sStatic.mSelectedRoute; if (oldRoute == route) return; boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo || oldRoute == sStatic.mBluetoothA2dpRoute); if (oldRoute == route && (!wasDefaultOrBluetoothRoute || oldRoute == sStatic.mSystemAudioRoute)) { return; } if (!route.matchesTypes(types)) { Log.w(TAG, "selectRoute ignored; cannot select route with supported types " + typesToString(route.getSupportedTypes()) + " into route types " + Loading @@ -918,8 +957,8 @@ public class MediaRouter { } final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute; if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 && (route == btRoute || route == sStatic.mDefaultAudioVideo)) { if (sStatic.isPlaybackActive() && btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 && (route == btRoute || route == sStatic.mDefaultAudioVideo)) { try { sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute); // TODO: Remove the following logging when no longer needed. Loading services/core/java/com/android/server/media/AudioPlaybackMonitor.java +104 −20 Original line number Diff line number Diff line Loading @@ -31,17 +31,22 @@ import android.util.SparseArray; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Monitors changes in audio playback and notify the newly started audio playback through the * {@link OnAudioPlaybackStartedListener}. * Monitors changes in audio playback, and notify the newly started audio playback through the * {@link OnAudioPlaybackStartedListener} and the activeness change through the * {@link OnAudioPlaybackActiveStateListener}. */ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { private static boolean DEBUG = MediaSessionService.DEBUG; private static String TAG = "AudioPlaybackMonitor"; private static AudioPlaybackMonitor sInstance; /** * Called when audio playback is started for a given UID. */ Loading @@ -49,22 +54,36 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { void onAudioPlaybackStarted(int uid); } /** * Called when audio player state is changed. */ interface OnAudioPlayerActiveStateChangedListener { void onAudioPlayerActiveStateChanged(int uid, boolean active); } private final Object mLock = new Object(); private final Context mContext; private final OnAudioPlaybackStartedListener mListener; private Set<Integer> mActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>(); private Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>(); private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners = new ArrayList<>(); private final List<OnAudioPlayerActiveStateChangedListener> mAudioPlayerActiveStateChangedListeners = new ArrayList<>(); private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>(); private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>(); // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video) // The UID whose audio playback becomes active at the last comes first. // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID. private final IntArray mSortedAudioPlaybackClientUids = new IntArray(); AudioPlaybackMonitor(Context context, IAudioService audioService, OnAudioPlaybackStartedListener listener) { static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) { if (sInstance == null) { sInstance = new AudioPlaybackMonitor(context, audioService); } return sInstance; } private AudioPlaybackMonitor(Context context, IAudioService audioService) { mContext = context; mListener = listener; try { audioService.registerPlaybackCallback(this); } catch (RemoteException e) { Loading @@ -84,9 +103,12 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) { final long token = Binder.clearCallingIdentity(); try { Set<Integer> newActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>(); List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>(); List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners; List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners; synchronized (mLock) { // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids, // and find newly activated audio playbacks. mActiveAudioPlaybackClientUids.clear(); for (AudioPlaybackConfiguration config : configs) { // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL Loading @@ -94,16 +116,14 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { // playback. // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM // specific audio/video players. if (!config.isActive() || config.getPlayerType() if (!config.isActive() || config.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { continue; } mActiveAudioPlaybackClientUids.add(config.getClientUid()); newActiveAudioPlaybackPlayerInterfaceIds.add(config.getPlayerInterfaceId()); if (!mActiveAudioPlaybackPlayerInterfaceIds.contains( config.getPlayerInterfaceId())) { mActiveAudioPlaybackClientUids.add(config.getClientUid()); Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId()); if (!isActiveState(oldState)) { if (DEBUG) { Log.d(TAG, "Found a new active media playback. " + AudioPlaybackConfiguration.toLogFriendlyString(config)); Loading @@ -120,17 +140,76 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { mSortedAudioPlaybackClientUids.add(0, config.getClientUid()); } } mActiveAudioPlaybackPlayerInterfaceIds.clear(); mActiveAudioPlaybackPlayerInterfaceIds = newActiveAudioPlaybackPlayerInterfaceIds; audioPlayerActiveStateChangedListeners = new ArrayList<>( mAudioPlayerActiveStateChangedListeners); audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners); } // Notify the change of audio playback states. for (AudioPlaybackConfiguration config : configs) { boolean wasActive = isActiveState( mAudioPlaybackStates.get(config.getPlayerInterfaceId())); boolean isActive = config.isActive(); if (wasActive != isActive) { for (OnAudioPlayerActiveStateChangedListener listener : audioPlayerActiveStateChangedListeners) { listener.onAudioPlayerActiveStateChanged(config.getClientUid(), isActive); } } } // Notify the start of audio playback for (int uid : newActiveAudioPlaybackClientUids) { mListener.onAudioPlaybackStarted(uid); for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) { listener.onAudioPlaybackStarted(uid); } } mAudioPlaybackStates.clear(); for (AudioPlaybackConfiguration config : configs) { mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState()); } } finally { Binder.restoreCallingIdentity(token); } } /** * Registers OnAudioPlaybackStartedListener. */ public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { synchronized (mLock) { mAudioPlaybackStartedListeners.add(listener); } } /** * Unregisters OnAudioPlaybackStartedListener. */ public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { synchronized (mLock) { mAudioPlaybackStartedListeners.remove(listener); } } /** * Registers OnAudioPlayerActiveStateChangedListener. */ public void registerOnAudioPlayerActiveStateChangedListener( OnAudioPlayerActiveStateChangedListener listener) { synchronized (mLock) { mAudioPlayerActiveStateChangedListeners.add(listener); } } /** * Unregisters OnAudioPlayerActiveStateChangedListener. */ public void unregisterOnAudioPlayerActiveStateChangedListener( OnAudioPlayerActiveStateChangedListener listener) { synchronized (mLock) { mAudioPlayerActiveStateChangedListeners.remove(listener); } } /** * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an * audio/video) The UID whose audio playback becomes active at the last comes first. Loading Loading @@ -167,7 +246,8 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) { break; } if (userId == UserHandle.getUserId(mSortedAudioPlaybackClientUids.get(i))) { int uid = mSortedAudioPlaybackClientUids.get(i); if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) { // Clean up unnecessary UIDs. // It doesn't need to be managed profile aware because it's just to prevent // the list from increasing indefinitely. The media button session updating Loading Loading @@ -198,4 +278,8 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } } } private boolean isActiveState(Integer state) { return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED); } } services/core/java/com/android/server/media/MediaRouterService.java +113 −5 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.media; import com.android.internal.util.DumpUtils; import com.android.server.Watchdog; import com.android.server.media.AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener; import android.Manifest; import android.app.ActivityManager; Loading @@ -26,7 +27,10 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.IAudioRoutesObserver; import android.media.IAudioService; import android.media.IMediaRouterClient; import android.media.IMediaRouterService; import android.media.MediaRouter; Loading @@ -39,9 +43,12 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.IntArray; import android.util.Log; import android.util.Slog; import android.util.SparseArray; Loading Loading @@ -89,10 +96,54 @@ public final class MediaRouterService extends IMediaRouterService.Stub private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<IBinder, ClientRecord>(); private int mCurrentUserId = -1; private boolean mHasBluetoothRoute = false; private final IAudioService mAudioService; private final AudioPlaybackMonitor mAudioPlaybackMonitor; public MediaRouterService(Context context) { mContext = context; Watchdog.getInstance().addMonitor(this); mAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService); mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener( new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() { @Override public void onAudioPlayerActiveStateChanged(int uid, boolean active) { if (active) { restoreRoute(uid); } else { IntArray sortedAudioPlaybackClientUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids(); boolean restored = false; for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) { if (mAudioPlaybackMonitor.isPlaybackActive( sortedAudioPlaybackClientUids.get(i))) { restoreRoute(sortedAudioPlaybackClientUids.get(i)); restored = true; break; } } if (!restored) { restoreBluetoothA2dp(); } } } }); AudioRoutesInfo audioRoutes = null; try { audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() { @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { mHasBluetoothRoute = newRoutes.bluetoothName != null; } }); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in the audio service."); } mHasBluetoothRoute = (audioRoutes != null && audioRoutes.bluetoothName != null); } public void systemRunning() { Loading Loading @@ -135,7 +186,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { registerClientLocked(client, pid, packageName, resolvedUserId, trusted); registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted); } } finally { Binder.restoreCallingIdentity(token); Loading Loading @@ -176,6 +227,23 @@ public final class MediaRouterService extends IMediaRouterService.Stub } } // Binder call @Override public boolean isPlaybackActive(IMediaRouterClient client) { if (client == null) { throw new IllegalArgumentException("client must not be null"); } final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { return isPlaybackActiveLocked(client); } } finally { Binder.restoreCallingIdentity(token); } } // Binder call @Override public void setDiscoveryRequest(IMediaRouterClient client, Loading Loading @@ -276,6 +344,36 @@ public final class MediaRouterService extends IMediaRouterService.Stub } } void restoreBluetoothA2dp() { try { mAudioService.setBluetoothA2dpOn(mHasBluetoothRoute); } catch (RemoteException e) { Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn."); } } void restoreRoute(int uid) { ClientRecord clientRecord = null; UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid)); if (userRecord.mClientRecords != null) { for (ClientRecord cr : userRecord.mClientRecords) { if (validatePackageName(uid, cr.mPackageName)) { clientRecord = cr; break; } } } if (clientRecord != null) { try { clientRecord.mClient.onRestoreRoute(); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died."); } } else { restoreBluetoothA2dp(); } } void switchUser() { synchronized (mLock) { int userId = ActivityManager.getCurrentUser(); Loading Loading @@ -304,7 +402,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub } private void registerClientLocked(IMediaRouterClient client, int pid, String packageName, int userId, boolean trusted) { int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = client.asBinder(); ClientRecord clientRecord = mAllClientRecords.get(binder); if (clientRecord == null) { Loading @@ -314,7 +412,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub userRecord = new UserRecord(userId); newUser = true; } clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted); clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted); try { binder.linkToDeath(clientRecord, 0); } catch (RemoteException ex) { Loading Loading @@ -350,6 +448,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub return null; } private boolean isPlaybackActiveLocked(IMediaRouterClient client) { ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); if (clientRecord != null) { return mAudioPlaybackMonitor.isPlaybackActive(clientRecord.mUid); } return false; } private void setDiscoveryRequestLocked(IMediaRouterClient client, int routeTypes, boolean activeScan) { final IBinder binder = client.asBinder(); Loading Loading @@ -489,6 +595,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub final class ClientRecord implements DeathRecipient { public final UserRecord mUserRecord; public final IMediaRouterClient mClient; public final int mUid; public final int mPid; public final String mPackageName; public final boolean mTrusted; Loading @@ -498,9 +605,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub public String mSelectedRouteId; public ClientRecord(UserRecord userRecord, IMediaRouterClient client, int pid, String packageName, boolean trusted) { int uid, int pid, String packageName, boolean trusted) { mUserRecord = userRecord; mClient = client; mUid = uid; mPid = pid; mPackageName = packageName; mTrusted = trusted; Loading Loading @@ -997,7 +1105,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub try { mTempClients.get(i).onStateChanged(); } catch (RemoteException ex) { // ignore errors, client probably died Slog.w(TAG, "Failed to call onStateChanged. Client probably died."); } } } finally { Loading Loading
media/java/android/media/IMediaRouterClient.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -21,4 +21,5 @@ package android.media; */ oneway interface IMediaRouterClient { void onStateChanged(); void onRestoreRoute(); }
media/java/android/media/IMediaRouterService.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ interface IMediaRouterService { void unregisterClient(IMediaRouterClient client); MediaRouterClientState getState(IMediaRouterClient client); boolean isPlaybackActive(IMediaRouterClient client); void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan); void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); Loading
media/java/android/media/MediaRouter.java +57 −18 Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ public class MediaRouter { RouteInfo mBluetoothA2dpRoute; RouteInfo mSelectedRoute; RouteInfo mSystemAudioRoute; final boolean mCanConfigureWifiDisplays; boolean mActivelyScanningWifiDisplays; Loading Loading @@ -149,6 +150,7 @@ public class MediaRouter { } addRouteStatic(mDefaultAudioVideo); mSystemAudioRoute = mDefaultAudioVideo; // This will select the active wifi display route if there is one. updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus()); Loading Loading @@ -197,8 +199,8 @@ public class MediaRouter { } else { name = com.android.internal.R.string.default_audio_route_name; } sStatic.mDefaultAudioVideo.mNameResId = name; dispatchRouteChanged(sStatic.mDefaultAudioVideo); mDefaultAudioVideo.mNameResId = name; dispatchRouteChanged(mDefaultAudioVideo); updated = true; } Loading @@ -207,22 +209,28 @@ public class MediaRouter { if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; if (mCurAudioRoutesInfo.bluetoothName != null) { if (sStatic.mBluetoothA2dpRoute == null) { final RouteInfo info = new RouteInfo(sStatic.mSystemCategory); if (mBluetoothA2dpRoute == null) { // BT connected final RouteInfo info = new RouteInfo(mSystemCategory); info.mName = mCurAudioRoutesInfo.bluetoothName; info.mDescription = sStatic.mResources.getText( info.mDescription = mResources.getText( com.android.internal.R.string.bluetooth_a2dp_audio_route_name); info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH; sStatic.mBluetoothA2dpRoute = info; addRouteStatic(sStatic.mBluetoothA2dpRoute); mBluetoothA2dpRoute = info; addRouteStatic(mBluetoothA2dpRoute); mSystemAudioRoute = mBluetoothA2dpRoute; selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false); } else { sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName; dispatchRouteChanged(sStatic.mBluetoothA2dpRoute); mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName; dispatchRouteChanged(mBluetoothA2dpRoute); } } else if (sStatic.mBluetoothA2dpRoute != null) { removeRouteStatic(sStatic.mBluetoothA2dpRoute); sStatic.mBluetoothA2dpRoute = null; } else if (mBluetoothA2dpRoute != null) { // BT disconnected removeRouteStatic(mBluetoothA2dpRoute); mBluetoothA2dpRoute = null; mSystemAudioRoute = mDefaultAudioVideo; selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false); } updated = true; } Loading @@ -230,11 +238,13 @@ public class MediaRouter { if (mBluetoothA2dpRoute != null) { final boolean a2dpEnabled = isBluetoothA2dpOn(); if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) { selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false); // A2DP off mSystemAudioRoute = mDefaultAudioVideo; updated = true; } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) && a2dpEnabled) { selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false); // A2DP on or BT connected mSystemAudioRoute = mBluetoothA2dpRoute; updated = true; } } Loading Loading @@ -471,7 +481,7 @@ public class MediaRouter { } RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) { RouteInfo route = new RouteInfo(sStatic.mSystemCategory); RouteInfo route = new RouteInfo(mSystemCategory); route.mGlobalRouteId = globalRoute.id; route.mName = globalRoute.name; route.mDescription = globalRoute.description; Loading Loading @@ -567,6 +577,17 @@ public class MediaRouter { return null; } boolean isPlaybackActive() { if (mClient != null) { try { return mMediaRouterService.isPlaybackActive(mClient); } catch (RemoteException ex) { Log.e(TAG, "Unable to retrieve playback active state.", ex); } } return false; } final class Client extends IMediaRouterClient.Stub { @Override public void onStateChanged() { Loading @@ -579,6 +600,19 @@ public class MediaRouter { } }); } @Override public void onRestoreRoute() { if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute) || mSelectedRoute == mSystemAudioRoute) { return; } try { sStatic.mAudioService.setBluetoothA2dpOn(mSelectedRoute == mBluetoothA2dpRoute); } catch (RemoteException e) { Log.e(TAG, "Error changing Bluetooth A2DP state", e); } } } } Loading Loading @@ -909,7 +943,12 @@ public class MediaRouter { Log.v(TAG, "Selecting route: " + route); assert(route != null); final RouteInfo oldRoute = sStatic.mSelectedRoute; if (oldRoute == route) return; boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo || oldRoute == sStatic.mBluetoothA2dpRoute); if (oldRoute == route && (!wasDefaultOrBluetoothRoute || oldRoute == sStatic.mSystemAudioRoute)) { return; } if (!route.matchesTypes(types)) { Log.w(TAG, "selectRoute ignored; cannot select route with supported types " + typesToString(route.getSupportedTypes()) + " into route types " + Loading @@ -918,8 +957,8 @@ public class MediaRouter { } final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute; if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 && (route == btRoute || route == sStatic.mDefaultAudioVideo)) { if (sStatic.isPlaybackActive() && btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 && (route == btRoute || route == sStatic.mDefaultAudioVideo)) { try { sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute); // TODO: Remove the following logging when no longer needed. Loading
services/core/java/com/android/server/media/AudioPlaybackMonitor.java +104 −20 Original line number Diff line number Diff line Loading @@ -31,17 +31,22 @@ import android.util.SparseArray; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Monitors changes in audio playback and notify the newly started audio playback through the * {@link OnAudioPlaybackStartedListener}. * Monitors changes in audio playback, and notify the newly started audio playback through the * {@link OnAudioPlaybackStartedListener} and the activeness change through the * {@link OnAudioPlaybackActiveStateListener}. */ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { private static boolean DEBUG = MediaSessionService.DEBUG; private static String TAG = "AudioPlaybackMonitor"; private static AudioPlaybackMonitor sInstance; /** * Called when audio playback is started for a given UID. */ Loading @@ -49,22 +54,36 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { void onAudioPlaybackStarted(int uid); } /** * Called when audio player state is changed. */ interface OnAudioPlayerActiveStateChangedListener { void onAudioPlayerActiveStateChanged(int uid, boolean active); } private final Object mLock = new Object(); private final Context mContext; private final OnAudioPlaybackStartedListener mListener; private Set<Integer> mActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>(); private Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>(); private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners = new ArrayList<>(); private final List<OnAudioPlayerActiveStateChangedListener> mAudioPlayerActiveStateChangedListeners = new ArrayList<>(); private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>(); private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>(); // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video) // The UID whose audio playback becomes active at the last comes first. // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID. private final IntArray mSortedAudioPlaybackClientUids = new IntArray(); AudioPlaybackMonitor(Context context, IAudioService audioService, OnAudioPlaybackStartedListener listener) { static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) { if (sInstance == null) { sInstance = new AudioPlaybackMonitor(context, audioService); } return sInstance; } private AudioPlaybackMonitor(Context context, IAudioService audioService) { mContext = context; mListener = listener; try { audioService.registerPlaybackCallback(this); } catch (RemoteException e) { Loading @@ -84,9 +103,12 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) { final long token = Binder.clearCallingIdentity(); try { Set<Integer> newActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>(); List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>(); List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners; List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners; synchronized (mLock) { // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids, // and find newly activated audio playbacks. mActiveAudioPlaybackClientUids.clear(); for (AudioPlaybackConfiguration config : configs) { // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL Loading @@ -94,16 +116,14 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { // playback. // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM // specific audio/video players. if (!config.isActive() || config.getPlayerType() if (!config.isActive() || config.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { continue; } mActiveAudioPlaybackClientUids.add(config.getClientUid()); newActiveAudioPlaybackPlayerInterfaceIds.add(config.getPlayerInterfaceId()); if (!mActiveAudioPlaybackPlayerInterfaceIds.contains( config.getPlayerInterfaceId())) { mActiveAudioPlaybackClientUids.add(config.getClientUid()); Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId()); if (!isActiveState(oldState)) { if (DEBUG) { Log.d(TAG, "Found a new active media playback. " + AudioPlaybackConfiguration.toLogFriendlyString(config)); Loading @@ -120,17 +140,76 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { mSortedAudioPlaybackClientUids.add(0, config.getClientUid()); } } mActiveAudioPlaybackPlayerInterfaceIds.clear(); mActiveAudioPlaybackPlayerInterfaceIds = newActiveAudioPlaybackPlayerInterfaceIds; audioPlayerActiveStateChangedListeners = new ArrayList<>( mAudioPlayerActiveStateChangedListeners); audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners); } // Notify the change of audio playback states. for (AudioPlaybackConfiguration config : configs) { boolean wasActive = isActiveState( mAudioPlaybackStates.get(config.getPlayerInterfaceId())); boolean isActive = config.isActive(); if (wasActive != isActive) { for (OnAudioPlayerActiveStateChangedListener listener : audioPlayerActiveStateChangedListeners) { listener.onAudioPlayerActiveStateChanged(config.getClientUid(), isActive); } } } // Notify the start of audio playback for (int uid : newActiveAudioPlaybackClientUids) { mListener.onAudioPlaybackStarted(uid); for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) { listener.onAudioPlaybackStarted(uid); } } mAudioPlaybackStates.clear(); for (AudioPlaybackConfiguration config : configs) { mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState()); } } finally { Binder.restoreCallingIdentity(token); } } /** * Registers OnAudioPlaybackStartedListener. */ public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { synchronized (mLock) { mAudioPlaybackStartedListeners.add(listener); } } /** * Unregisters OnAudioPlaybackStartedListener. */ public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { synchronized (mLock) { mAudioPlaybackStartedListeners.remove(listener); } } /** * Registers OnAudioPlayerActiveStateChangedListener. */ public void registerOnAudioPlayerActiveStateChangedListener( OnAudioPlayerActiveStateChangedListener listener) { synchronized (mLock) { mAudioPlayerActiveStateChangedListeners.add(listener); } } /** * Unregisters OnAudioPlayerActiveStateChangedListener. */ public void unregisterOnAudioPlayerActiveStateChangedListener( OnAudioPlayerActiveStateChangedListener listener) { synchronized (mLock) { mAudioPlayerActiveStateChangedListeners.remove(listener); } } /** * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an * audio/video) The UID whose audio playback becomes active at the last comes first. Loading Loading @@ -167,7 +246,8 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) { break; } if (userId == UserHandle.getUserId(mSortedAudioPlaybackClientUids.get(i))) { int uid = mSortedAudioPlaybackClientUids.get(i); if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) { // Clean up unnecessary UIDs. // It doesn't need to be managed profile aware because it's just to prevent // the list from increasing indefinitely. The media button session updating Loading Loading @@ -198,4 +278,8 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } } } private boolean isActiveState(Integer state) { return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED); } }
services/core/java/com/android/server/media/MediaRouterService.java +113 −5 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.media; import com.android.internal.util.DumpUtils; import com.android.server.Watchdog; import com.android.server.media.AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener; import android.Manifest; import android.app.ActivityManager; Loading @@ -26,7 +27,10 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.IAudioRoutesObserver; import android.media.IAudioService; import android.media.IMediaRouterClient; import android.media.IMediaRouterService; import android.media.MediaRouter; Loading @@ -39,9 +43,12 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.IntArray; import android.util.Log; import android.util.Slog; import android.util.SparseArray; Loading Loading @@ -89,10 +96,54 @@ public final class MediaRouterService extends IMediaRouterService.Stub private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<IBinder, ClientRecord>(); private int mCurrentUserId = -1; private boolean mHasBluetoothRoute = false; private final IAudioService mAudioService; private final AudioPlaybackMonitor mAudioPlaybackMonitor; public MediaRouterService(Context context) { mContext = context; Watchdog.getInstance().addMonitor(this); mAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService); mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener( new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() { @Override public void onAudioPlayerActiveStateChanged(int uid, boolean active) { if (active) { restoreRoute(uid); } else { IntArray sortedAudioPlaybackClientUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids(); boolean restored = false; for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) { if (mAudioPlaybackMonitor.isPlaybackActive( sortedAudioPlaybackClientUids.get(i))) { restoreRoute(sortedAudioPlaybackClientUids.get(i)); restored = true; break; } } if (!restored) { restoreBluetoothA2dp(); } } } }); AudioRoutesInfo audioRoutes = null; try { audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() { @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { mHasBluetoothRoute = newRoutes.bluetoothName != null; } }); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in the audio service."); } mHasBluetoothRoute = (audioRoutes != null && audioRoutes.bluetoothName != null); } public void systemRunning() { Loading Loading @@ -135,7 +186,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { registerClientLocked(client, pid, packageName, resolvedUserId, trusted); registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted); } } finally { Binder.restoreCallingIdentity(token); Loading Loading @@ -176,6 +227,23 @@ public final class MediaRouterService extends IMediaRouterService.Stub } } // Binder call @Override public boolean isPlaybackActive(IMediaRouterClient client) { if (client == null) { throw new IllegalArgumentException("client must not be null"); } final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { return isPlaybackActiveLocked(client); } } finally { Binder.restoreCallingIdentity(token); } } // Binder call @Override public void setDiscoveryRequest(IMediaRouterClient client, Loading Loading @@ -276,6 +344,36 @@ public final class MediaRouterService extends IMediaRouterService.Stub } } void restoreBluetoothA2dp() { try { mAudioService.setBluetoothA2dpOn(mHasBluetoothRoute); } catch (RemoteException e) { Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn."); } } void restoreRoute(int uid) { ClientRecord clientRecord = null; UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid)); if (userRecord.mClientRecords != null) { for (ClientRecord cr : userRecord.mClientRecords) { if (validatePackageName(uid, cr.mPackageName)) { clientRecord = cr; break; } } } if (clientRecord != null) { try { clientRecord.mClient.onRestoreRoute(); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died."); } } else { restoreBluetoothA2dp(); } } void switchUser() { synchronized (mLock) { int userId = ActivityManager.getCurrentUser(); Loading Loading @@ -304,7 +402,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub } private void registerClientLocked(IMediaRouterClient client, int pid, String packageName, int userId, boolean trusted) { int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = client.asBinder(); ClientRecord clientRecord = mAllClientRecords.get(binder); if (clientRecord == null) { Loading @@ -314,7 +412,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub userRecord = new UserRecord(userId); newUser = true; } clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted); clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted); try { binder.linkToDeath(clientRecord, 0); } catch (RemoteException ex) { Loading Loading @@ -350,6 +448,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub return null; } private boolean isPlaybackActiveLocked(IMediaRouterClient client) { ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); if (clientRecord != null) { return mAudioPlaybackMonitor.isPlaybackActive(clientRecord.mUid); } return false; } private void setDiscoveryRequestLocked(IMediaRouterClient client, int routeTypes, boolean activeScan) { final IBinder binder = client.asBinder(); Loading Loading @@ -489,6 +595,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub final class ClientRecord implements DeathRecipient { public final UserRecord mUserRecord; public final IMediaRouterClient mClient; public final int mUid; public final int mPid; public final String mPackageName; public final boolean mTrusted; Loading @@ -498,9 +605,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub public String mSelectedRouteId; public ClientRecord(UserRecord userRecord, IMediaRouterClient client, int pid, String packageName, boolean trusted) { int uid, int pid, String packageName, boolean trusted) { mUserRecord = userRecord; mClient = client; mUid = uid; mPid = pid; mPackageName = packageName; mTrusted = trusted; Loading Loading @@ -997,7 +1105,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub try { mTempClients.get(i).onStateChanged(); } catch (RemoteException ex) { // ignore errors, client probably died Slog.w(TAG, "Failed to call onStateChanged. Client probably died."); } } } finally { Loading