Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d1b30149 authored by Beth Thibodeau's avatar Beth Thibodeau Committed by Automerger Merge Worker
Browse files

Merge "Allow apps with BLUETOOTH permission to control system routing" into...

Merge "Allow apps with BLUETOOTH permission to control system routing" into udc-dev am: 0871ce45 am: 2e27842b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23135277



Change-Id: Ic589cf73a2421294a5fe5e5c3cb6d9716512946b
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 685aadb4 2e27842b
Loading
Loading
Loading
Loading
+32 −3
Original line number Diff line number Diff line
@@ -134,6 +134,21 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
        return null;
    }

    /**
     * Looks through the pending transitions for a opening transaction that matches the provided
     * `taskView`.
     * @param taskView the pending transition should be for this.
     */
    private PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) {
        for (int i = mPending.size() - 1; i >= 0; --i) {
            if (mPending.get(i).mTaskView != taskView) continue;
            if (TransitionUtil.isOpeningType(mPending.get(i).mType)) {
                return mPending.get(i);
            }
        }
        return null;
    }

    /**
     * Looks through the pending transitions for one matching `taskView`.
     * @param taskView the pending transition should be for this.
@@ -149,6 +164,19 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
        return null;
    }

    /**
     * Returns all the pending transitions for a given `taskView`.
     * @param taskView the pending transition should be for this.
     */
    ArrayList<PendingTransition> findAllPending(TaskViewTaskController taskView) {
        ArrayList<PendingTransition> list = new ArrayList<>();
        for (int i = mPending.size() - 1; i >= 0; --i) {
            if (mPending.get(i).mTaskView != taskView) continue;
            list.add(mPending.get(i));
        }
        return list;
    }

    private PendingTransition findPending(IBinder claimed) {
        for (int i = 0; i < mPending.size(); ++i) {
            if (mPending.get(i).mClaimed != claimed) continue;
@@ -249,9 +277,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
            // Task view isn't visible, the bounds will next visibility update.
            return;
        }
        if (hasPending()) {
            // There is already a transition in-flight, the window bounds will be set in
            // prepareOpenAnimation.
        PendingTransition pendingOpen = findPendingOpeningTransition(taskView);
        if (pendingOpen != null) {
            // There is already an opening transition in-flight, the window bounds will be
            // set in prepareOpenAnimation (via the window crop) if needed.
            return;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
+99 −2
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.List;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -58,6 +60,12 @@ public class TaskViewTransitionsTest extends ShellTestCase {
    ActivityManager.RunningTaskInfo mTaskInfo;
    @Mock
    WindowContainerToken mToken;
    @Mock
    TaskViewTaskController mTaskViewTaskController2;
    @Mock
    ActivityManager.RunningTaskInfo mTaskInfo2;
    @Mock
    WindowContainerToken mToken2;

    TaskViewTransitions mTaskViewTransitions;

@@ -73,10 +81,16 @@ public class TaskViewTransitionsTest extends ShellTestCase {
        mTaskInfo.token = mToken;
        mTaskInfo.taskId = 314;
        mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
        when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);

        mTaskInfo2 = new ActivityManager.RunningTaskInfo();
        mTaskInfo2.token = mToken2;
        mTaskInfo2.taskId = 315;
        mTaskInfo2.taskDescription = mock(ActivityManager.TaskDescription.class);
        when(mTaskViewTaskController2.getTaskInfo()).thenReturn(mTaskInfo2);

        mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
        mTaskViewTransitions.addTaskView(mTaskViewTaskController);
        when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
    }

    @Test
@@ -119,7 +133,7 @@ public class TaskViewTransitionsTest extends ShellTestCase {
    }

    @Test
    public void testSetTaskBounds_taskVisibleWithPending_noTransaction() {
    public void testSetTaskBounds_taskVisibleWithPendingOpen_noTransaction() {
        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);

        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
@@ -134,6 +148,43 @@ public class TaskViewTransitionsTest extends ShellTestCase {
                .isNull();
    }

    @Test
    public void testSetTaskBounds_taskVisibleWithPendingChange_transition() {
        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);

        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);

        // Consume the pending transition from visibility change
        TaskViewTransitions.PendingTransition pending =
                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
        assertThat(pending).isNotNull();
        mTaskViewTransitions.startAnimation(pending.mClaimed,
                mock(TransitionInfo.class),
                new SurfaceControl.Transaction(),
                new SurfaceControl.Transaction(),
                mock(Transitions.TransitionFinishCallback.class));
        // Verify it was consumed
        TaskViewTransitions.PendingTransition checkPending =
                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
        assertThat(checkPending).isNull();

        // Test that set bounds creates a new transition
        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
                new Rect(0, 0, 100, 100));
        assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
                .isNotNull();

        // Test that set bounds again (with different bounds) creates another transition
        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
                new Rect(0, 0, 300, 200));
        List<TaskViewTransitions.PendingTransition> pendingList =
                mTaskViewTransitions.findAllPending(mTaskViewTaskController)
                        .stream()
                        .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
                        .toList();
        assertThat(pendingList.size()).isEqualTo(2);
    }

    @Test
    public void testSetTaskBounds_sameBounds_noTransaction() {
        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
@@ -161,6 +212,16 @@ public class TaskViewTransitionsTest extends ShellTestCase {
                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
        assertThat(pendingBounds).isNotNull();

        // Test that setting same bounds with in-flight transition doesn't cause another one
        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
                new Rect(0, 0, 100, 100));
        List<TaskViewTransitions.PendingTransition> pendingList =
                mTaskViewTransitions.findAllPending(mTaskViewTaskController)
                        .stream()
                        .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
                        .toList();
        assertThat(pendingList.size()).isEqualTo(1);

        // Consume the pending bounds transaction
        mTaskViewTransitions.startAnimation(pendingBounds.mClaimed,
                mock(TransitionInfo.class),
@@ -180,6 +241,42 @@ public class TaskViewTransitionsTest extends ShellTestCase {
        assertThat(pendingBounds2).isNull();
    }


    @Test
    public void testSetTaskBounds_taskVisibleWithDifferentTaskViewPendingChange_transition() {
        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);

        mTaskViewTransitions.addTaskView(mTaskViewTaskController2);

        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);

        // Consume the pending transition from visibility change
        TaskViewTransitions.PendingTransition pending =
                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
        assertThat(pending).isNotNull();
        mTaskViewTransitions.startAnimation(pending.mClaimed,
                mock(TransitionInfo.class),
                new SurfaceControl.Transaction(),
                new SurfaceControl.Transaction(),
                mock(Transitions.TransitionFinishCallback.class));
        // Verify it was consumed
        TaskViewTransitions.PendingTransition checkPending =
                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
        assertThat(checkPending).isNull();

        // Set the second taskview as visible & check that it has a pending transition
        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController2, true);
        TaskViewTransitions.PendingTransition pending2 =
                mTaskViewTransitions.findPending(mTaskViewTaskController2, TRANSIT_TO_FRONT);
        assertThat(pending2).isNotNull();

        // Test that set bounds on the first taskview will create a new transition
        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
                new Rect(0, 0, 100, 100));
        assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
                .isNotNull();
    }

    @Test
    public void testSetTaskVisibility_taskRemoved_noNPE() {
        mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+24 −0
Original line number Diff line number Diff line
@@ -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 */
    @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
            CONNECTION_STATE_CONNECTED})
+6 −7
Original line number Diff line number Diff line
@@ -42,13 +42,10 @@ import com.android.internal.annotations.VisibleForTesting;

import java.util.Objects;


/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {

    private static final String TAG = "APDeviceRoutesController";

    private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";

    @NonNull
    private final Context mContext;
    @NonNull
@@ -182,8 +179,10 @@ import java.util.Objects;

        synchronized (this) {
            return new MediaRoute2Info.Builder(
                    DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
                    .setVolumeHandling(mAudioManager.isVolumeFixed()
                            MediaRoute2Info.ROUTE_ID_DEVICE,
                            mContext.getResources().getText(name).toString())
                    .setVolumeHandling(
                            mAudioManager.isVolumeFixed()
                                    ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
                                    : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                    .setVolume(mDeviceVolume)
+87 −6
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
@@ -75,8 +76,10 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@@ -97,6 +100,17 @@ class MediaRouter2ServiceImpl {
    private static final String KEY_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(
            MEDIA_BETTER_TOGETHER_NAMESPACE,
            /* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
@@ -142,6 +156,7 @@ class MediaRouter2ServiceImpl {
        }
    };

    @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
    /* package */ MediaRouter2ServiceImpl(Context context) {
        mContext = context;
        mActivityManager = mContext.getSystemService(ActivityManager.class);
@@ -155,12 +170,28 @@ class MediaRouter2ServiceImpl {
        screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);

        mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
        mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);

        DeviceConfig.addOnPropertiesChangedListener(MEDIA_BETTER_TOGETHER_NAMESPACE,
                ActivityThread.currentApplication().getMainExecutor(),
                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.

    @NonNull
@@ -1511,6 +1542,7 @@ class MediaRouter2ServiceImpl {
        public final int mPid;
        public final boolean mHasConfigureWifiDisplayPermission;
        public final boolean mHasModifyAudioRoutingPermission;
        public final AtomicBoolean mHasBluetoothRoutingPermission;
        public final int mRouterId;

        public RouteDiscoveryPreference mDiscoveryPreference;
@@ -1528,15 +1560,47 @@ class MediaRouter2ServiceImpl {
            mPid = pid;
            mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
            mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
            mHasBluetoothRoutingPermission = new AtomicBoolean(fetchBluetoothPermission());
            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
         * routes.
         */
        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() {
@@ -1559,6 +1623,14 @@ class MediaRouter2ServiceImpl {
            pw.println(indent + "mPid=" + mPid);
            pw.println(indent + "mHasConfigureWifiDisplayPermission="
                    + mHasConfigureWifiDisplayPermission);
            pw.println(
                    indent
                            + "mHasModifyAudioRoutingPermission="
                            + mHasModifyAudioRoutingPermission);
            pw.println(
                    indent
                            + "mHasBluetoothRoutingPermission="
                            + mHasBluetoothRoutingPermission.get());
            pw.println(indent + "hasSystemRoutingPermission=" + hasSystemRoutingPermission());
            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
         * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
@@ -2471,11 +2556,7 @@ class MediaRouter2ServiceImpl {
                @NonNull List<RouterRecord> routerRecords,
                @NonNull RoutingSessionInfo sessionInfo) {
            for (RouterRecord routerRecord : routerRecords) {
                try {
                    routerRecord.mRouter.notifySessionInfoChanged(sessionInfo);
                } catch (RemoteException ex) {
                    Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
                }
                routerRecord.notifySessionInfoChanged(sessionInfo);
            }
        }

Loading