Merge "Allow apps with BLUETOOTH permission to control system routing" into udc-dev

This commit is contained in:
Beth Thibodeau
2023-05-12 23:36:06 +00:00
committed by Android (Google) Code Review
5 changed files with 133 additions and 26 deletions

View File

@@ -57,6 +57,30 @@ public final class MediaRoute2Info implements Parcelable {
} }
}; };
/**
* The {@link #getOriginalId() original id} of the route that represents the built-in media
* route.
*
* <p>A route with this id will only be visible to apps with permission to do system routing,
* which means having {@link android.Manifest.permission#BLUETOOTH_CONNECT} and {@link
* android.Manifest.permission#BLUETOOTH_SCAN}, or {@link
* android.Manifest.permission#MODIFY_AUDIO_ROUTING}.
*
* @hide
*/
public static final String ROUTE_ID_DEVICE = "DEVICE_ROUTE";
/**
* The {@link #getOriginalId() original id} of the route that represents the default system
* media route.
*
* <p>A route with this id will be visible to apps with no permission over system routing. See
* {@link #ROUTE_ID_DEVICE} for details.
*
* @hide
*/
public static final String ROUTE_ID_DEFAULT = "DEFAULT_ROUTE";
/** @hide */ /** @hide */
@IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING, @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
CONNECTION_STATE_CONNECTED}) CONNECTION_STATE_CONNECTED})

View File

@@ -42,13 +42,10 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects; import java.util.Objects;
/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController { /* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {
private static final String TAG = "APDeviceRoutesController"; private static final String TAG = "APDeviceRoutesController";
private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
@NonNull @NonNull
private final Context mContext; private final Context mContext;
@NonNull @NonNull
@@ -182,10 +179,12 @@ import java.util.Objects;
synchronized (this) { synchronized (this) {
return new MediaRoute2Info.Builder( return new MediaRoute2Info.Builder(
DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString()) MediaRoute2Info.ROUTE_ID_DEVICE,
.setVolumeHandling(mAudioManager.isVolumeFixed() mContext.getResources().getText(name).toString())
? MediaRoute2Info.PLAYBACK_VOLUME_FIXED .setVolumeHandling(
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) mAudioManager.isVolumeFixed()
? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolume(mDeviceVolume) .setVolume(mDeviceVolume)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setType(type) .setType(type)

View File

@@ -28,6 +28,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.Manifest; import android.Manifest;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@@ -75,8 +76,10 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -97,6 +100,17 @@ class MediaRouter2ServiceImpl {
private static final String KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE = private static final String KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE =
"scanning_package_minimum_importance"; "scanning_package_minimum_importance";
/**
* Contains the list of bluetooth permissions that are required to do system routing.
*
* <p>Alternatively, apps that hold {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} are
* also allowed to do system routing.
*/
private static final String[] BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING =
new String[] {
Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN
};
private static int sPackageImportanceForScanning = DeviceConfig.getInt( private static int sPackageImportanceForScanning = DeviceConfig.getInt(
MEDIA_BETTER_TOGETHER_NAMESPACE, MEDIA_BETTER_TOGETHER_NAMESPACE,
/* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE, /* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
@@ -142,6 +156,7 @@ class MediaRouter2ServiceImpl {
} }
}; };
@RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
/* package */ MediaRouter2ServiceImpl(Context context) { /* package */ MediaRouter2ServiceImpl(Context context) {
mContext = context; mContext = context;
mActivityManager = mContext.getSystemService(ActivityManager.class); mActivityManager = mContext.getSystemService(ActivityManager.class);
@@ -155,12 +170,28 @@ class MediaRouter2ServiceImpl {
screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF); screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter); mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
DeviceConfig.addOnPropertiesChangedListener(MEDIA_BETTER_TOGETHER_NAMESPACE, DeviceConfig.addOnPropertiesChangedListener(MEDIA_BETTER_TOGETHER_NAMESPACE,
ActivityThread.currentApplication().getMainExecutor(), ActivityThread.currentApplication().getMainExecutor(),
this::onDeviceConfigChange); this::onDeviceConfigChange);
} }
/**
* Called when there's a change in the permissions of an app.
*
* @param uid The uid of the app whose permissions changed.
*/
private void onPermissionsChanged(int uid) {
synchronized (mLock) {
Optional<RouterRecord> affectedRouter =
mAllRouterRecords.values().stream().filter(it -> it.mUid == uid).findFirst();
if (affectedRouter.isPresent()) {
affectedRouter.get().maybeUpdateSystemRoutingPermissionLocked();
}
}
}
// Start of methods that implement MediaRouter2 operations. // Start of methods that implement MediaRouter2 operations.
@NonNull @NonNull
@@ -1511,6 +1542,7 @@ class MediaRouter2ServiceImpl {
public final int mPid; public final int mPid;
public final boolean mHasConfigureWifiDisplayPermission; public final boolean mHasConfigureWifiDisplayPermission;
public final boolean mHasModifyAudioRoutingPermission; public final boolean mHasModifyAudioRoutingPermission;
public final AtomicBoolean mHasBluetoothRoutingPermission;
public final int mRouterId; public final int mRouterId;
public RouteDiscoveryPreference mDiscoveryPreference; public RouteDiscoveryPreference mDiscoveryPreference;
@@ -1528,15 +1560,47 @@ class MediaRouter2ServiceImpl {
mPid = pid; mPid = pid;
mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission; mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission; mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
mHasBluetoothRoutingPermission = new AtomicBoolean(fetchBluetoothPermission());
mRouterId = mNextRouterOrManagerId.getAndIncrement(); mRouterId = mNextRouterOrManagerId.getAndIncrement();
} }
private boolean fetchBluetoothPermission() {
boolean hasBluetoothRoutingPermission = true;
for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
hasBluetoothRoutingPermission &=
mContext.checkPermission(permission, mPid, mUid)
== PackageManager.PERMISSION_GRANTED;
}
return hasBluetoothRoutingPermission;
}
/** /**
* Returns whether the corresponding router has permission to query and control system * Returns whether the corresponding router has permission to query and control system
* routes. * routes.
*/ */
public boolean hasSystemRoutingPermission() { public boolean hasSystemRoutingPermission() {
return mHasModifyAudioRoutingPermission; return mHasModifyAudioRoutingPermission || mHasBluetoothRoutingPermission.get();
}
public void maybeUpdateSystemRoutingPermissionLocked() {
boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
mHasBluetoothRoutingPermission.set(fetchBluetoothPermission());
boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission();
if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) {
Map<String, MediaRoute2Info> routesToReport =
newSystemRoutingPermissionValue
? mUserRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters
: mUserRecord.mHandler.mLastNotifiedRoutesToNonPrivilegedRouters;
notifyRoutesUpdated(routesToReport.values().stream().toList());
List<RoutingSessionInfo> sessionInfos =
mUserRecord.mHandler.mSystemProvider.getSessionInfos();
RoutingSessionInfo systemSessionToReport =
newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
? sessionInfos.get(0)
: mUserRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
notifySessionInfoChanged(systemSessionToReport);
}
} }
public void dispose() { public void dispose() {
@@ -1559,6 +1623,14 @@ class MediaRouter2ServiceImpl {
pw.println(indent + "mPid=" + mPid); pw.println(indent + "mPid=" + mPid);
pw.println(indent + "mHasConfigureWifiDisplayPermission=" pw.println(indent + "mHasConfigureWifiDisplayPermission="
+ mHasConfigureWifiDisplayPermission); + mHasConfigureWifiDisplayPermission);
pw.println(
indent
+ "mHasModifyAudioRoutingPermission="
+ mHasModifyAudioRoutingPermission);
pw.println(
indent
+ "mHasBluetoothRoutingPermission="
+ mHasBluetoothRoutingPermission.get());
pw.println(indent + "hasSystemRoutingPermission=" + hasSystemRoutingPermission()); pw.println(indent + "hasSystemRoutingPermission=" + hasSystemRoutingPermission());
pw.println(indent + "mRouterId=" + mRouterId); pw.println(indent + "mRouterId=" + mRouterId);
@@ -1580,6 +1652,19 @@ class MediaRouter2ServiceImpl {
} }
} }
/**
* Sends the corresponding router an update for the given session.
*
* <p>Note: These updates are not directly visible to the app.
*/
public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
try {
mRouter.notifySessionInfoChanged(sessionInfo);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
}
}
/** /**
* Returns a filtered copy of {@code routes} that contains only the routes that are {@link * Returns a filtered copy of {@code routes} that contains only the routes that are {@link
* MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record. * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
@@ -2471,11 +2556,7 @@ class MediaRouter2ServiceImpl {
@NonNull List<RouterRecord> routerRecords, @NonNull List<RouterRecord> routerRecords,
@NonNull RoutingSessionInfo sessionInfo) { @NonNull RoutingSessionInfo sessionInfo) {
for (RouterRecord routerRecord : routerRecords) { for (RouterRecord routerRecord : routerRecords) {
try { routerRecord.notifySessionInfoChanged(sessionInfo);
routerRecord.mRouter.notifySessionInfoChanged(sessionInfo);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
}
} }
} }

View File

@@ -18,6 +18,7 @@ package com.android.server.media;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import android.Manifest;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.RequiresPermission; import android.annotation.RequiresPermission;
@@ -137,6 +138,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private final String mDefaultAudioRouteId; private final String mDefaultAudioRouteId;
private final String mBluetoothA2dpRouteId; private final String mBluetoothA2dpRouteId;
@RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
public MediaRouterService(Context context) { public MediaRouterService(Context context) {
mService2 = new MediaRouter2ServiceImpl(context); mService2 = new MediaRouter2ServiceImpl(context);
mContext = context; mContext = context;

View File

@@ -55,7 +55,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
SystemMediaRoute2Provider.class.getPackage().getName(), SystemMediaRoute2Provider.class.getPackage().getName(),
SystemMediaRoute2Provider.class.getName()); SystemMediaRoute2Provider.class.getName());
static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION"; static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
private final AudioManager mAudioManager; private final AudioManager mAudioManager;
@@ -170,7 +169,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
Bundle sessionHints) { Bundle sessionHints) {
// Assume a router without MODIFY_AUDIO_ROUTING permission can't request with // Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
// a route ID different from the default route ID. The service should've filtered. // a route ID different from the default route ID. The service should've filtered.
if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) { if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo); mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
return; return;
} }
@@ -213,7 +212,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
@Override @Override
public void transferToRoute(long requestId, String sessionId, String routeId) { public void transferToRoute(long requestId, String sessionId, String routeId) {
if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) { if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
// The currently selected route is the default route. // The currently selected route is the default route.
return; return;
} }
@@ -326,10 +325,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
builder.addTransferableRoute(deviceRoute.getId()); builder.addTransferableRoute(deviceRoute.getId());
} }
mSelectedRouteId = selectedRoute.getId(); mSelectedRouteId = selectedRoute.getId();
mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute) mDefaultRoute =
.setSystemRoute(true) new MediaRoute2Info.Builder(MediaRoute2Info.ROUTE_ID_DEFAULT, selectedRoute)
.setProviderId(mUniqueId) .setSystemRoute(true)
.build(); .setProviderId(mUniqueId)
.build();
builder.addSelectedRoute(mSelectedRouteId); builder.addSelectedRoute(mSelectedRouteId);
for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) { for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
@@ -363,12 +363,13 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
} }
mSessionInfos.clear(); mSessionInfos.clear();
mSessionInfos.add(newSessionInfo); mSessionInfos.add(newSessionInfo);
mDefaultSessionInfo = new RoutingSessionInfo.Builder( mDefaultSessionInfo =
SYSTEM_SESSION_ID, "" /* clientPackageName */) new RoutingSessionInfo.Builder(
.setProviderId(mUniqueId) SYSTEM_SESSION_ID, "" /* clientPackageName */)
.setSystemSession(true) .setProviderId(mUniqueId)
.addSelectedRoute(DEFAULT_ROUTE_ID) .setSystemSession(true)
.build(); .addSelectedRoute(MediaRoute2Info.ROUTE_ID_DEFAULT)
.build();
return true; return true;
} }
} }