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

Commit 709df92c authored by Gavin Williams's avatar Gavin Williams Committed by Android (Google) Code Review
Browse files

Merge "Braille: Allow quick USB connections in SUW" into main

parents 429b8065 a0a8149c
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.accessibility;

import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN;
import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
import static android.content.IntentFilter.SYSTEM_HIGH_PRIORITY;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -39,10 +40,13 @@ import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Binder;
@@ -81,6 +85,7 @@ import java.util.Set;
 */
class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
    private static final String LOG_TAG = "AccessibilityServiceConnection";
    private static final int UID_UNKNOWN = -1;

    /*
     Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps
@@ -99,6 +104,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
    private List<Bundle> mTestBrailleDisplays = null;

    private final Handler mMainHandler;
    private UsbDeviceReceiver mUsbDeviceReceiverReceiver;

    private static final class AccessibilityInputMethodSessionCallback
            extends IAccessibilityInputMethodSessionCallback.Stub {
@@ -124,6 +130,24 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
        }
    }

    private class UsbDeviceReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (!Flags.enableBrailleSuwImmediateConnections()) {
                return;
            }

            UsbDevice device =
                    intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice.class);
            if (device != null) {
                String action = intent.getAction();
                if (action != null && action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
                    onDeviceAttached(device);
                }
            }
        }
    }

    AccessibilityServiceConnection(@Nullable AccessibilityUserState userState,
            Context context, ComponentName componentName,
            AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
@@ -177,6 +201,14 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
        mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(),
                mAccessibilityServiceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
                userState.mUserId);

        if (Flags.enableBrailleSuwImmediateConnections() && isInSetupWizard()) {
            IntentFilter filter = new IntentFilter();
            filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
            filter.setPriority(SYSTEM_HIGH_PRIORITY);
            mUsbDeviceReceiverReceiver = new UsbDeviceReceiver();
            mContext.registerReceiver(mUsbDeviceReceiverReceiver, filter);
        }
    }

    public void unbindLocked() {
@@ -191,6 +223,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
        mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(), -1,
                userState.mUserId);
        resetLocked();
        if (Flags.enableBrailleSuwImmediateConnections() && mUsbDeviceReceiverReceiver != null) {
            mContext.unregisterReceiver(mUsbDeviceReceiverReceiver);
        }
    }

    public boolean canRetrieveInteractiveWindowsLocked() {
@@ -799,6 +834,37 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
        }
    }

    private void onDeviceAttached(UsbDevice device) {
        if (!Flags.enableBrailleSuwImmediateConnections()) {
            return;
        }

        if (isInSetupWizard()) {
            int clientUid = getClientUid();
            if (clientUid == UID_UNKNOWN) {
                return;
            }

            UsbManager usbManager = mContext.getSystemService(UsbManager.class);
            usbManager.grantPermission(device, /* uid of client's App */ clientUid);
        }
    }

    private int getClientUid() {
        ResolveInfo resolveInfo = mAccessibilityServiceInfo.getResolveInfo();
        if (resolveInfo != null && resolveInfo.serviceInfo != null
                && resolveInfo.serviceInfo.applicationInfo != null) {
            return resolveInfo.serviceInfo.applicationInfo.uid;
        }

        return UID_UNKNOWN;
    }

    private boolean isInSetupWizard() {
        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
                Settings.Secure.USER_SETUP_COMPLETE, 0, mUserId) == 0;
    }

    @Override
    @EnforcePermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) {
+122 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.accessibility;

import static android.content.IntentFilter.SYSTEM_HIGH_PRIORITY;

import static com.google.common.truth.Truth.assertThat;

import static junit.framework.Assert.assertFalse;
@@ -26,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -41,9 +44,11 @@ import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IBrailleDisplayConnection;
import android.accessibilityservice.IBrailleDisplayController;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
@@ -56,13 +61,18 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.FakePermissionEnforcer;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
import android.view.WindowManager;

import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.accessibility.magnification.MagnificationProcessor;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -98,6 +108,9 @@ public class AccessibilityServiceConnectionTest {
    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    AccessibilityServiceConnection mConnection;

    @Mock AccessibilityUserState mMockUserState;
@@ -132,6 +145,7 @@ public class AccessibilityServiceConnectionTest {
    FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer();

    MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
    MockContentResolver mContentResolver;

    @Before
    public void setup() {
@@ -154,6 +168,10 @@ public class AccessibilityServiceConnectionTest {
        when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE))
                .thenReturn(mFakePermissionEnforcer);

        mContentResolver = new MockContentResolver();
        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
        when(mMockContext.getContentResolver()).thenReturn(mContentResolver);

        mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext,
                        COMPONENT_NAME, mServiceInfo, SERVICE_ID, mHandler, new Object(),
                        mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
@@ -476,4 +494,108 @@ public class AccessibilityServiceConnectionTest {
        verify(mMockSecurityPolicy).canEnableDisableInputMethod(
                eq(imeId), any(), eq(callingUserId));
    }

    @Test
    @EnableFlags(
            com.android.server.accessibility.Flags.FLAG_ENABLE_BRAILLE_SUW_IMMEDIATE_CONNECTIONS)
    public void bindUnbindInSuw_registerUsbReceiver() {
        // Set USER_SETUP_COMPLETE = 0 to simulate running in SUW.
        Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 0,
                mMockUserState.mUserId);

        mConnection.bindLocked();

        // Register the receiver.
        ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(
                IntentFilter.class);
        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
                BroadcastReceiver.class);
        verify(mMockContext).registerReceiver(broadcastReceiverCaptor.capture(),
                intentFilterCaptor.capture());
        IntentFilter intentFilter = intentFilterCaptor.getValue();
        assertThat(intentFilter.getAction(0)).isEqualTo(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        assertThat(intentFilter.getPriority()).isEqualTo(SYSTEM_HIGH_PRIORITY);

        // Unregister the receiver.
        mConnection.unbindLocked();
        verify(mMockContext).unregisterReceiver(same(broadcastReceiverCaptor.getValue()));
    }

    @Test
    @EnableFlags(
            com.android.server.accessibility.Flags.FLAG_ENABLE_BRAILLE_SUW_IMMEDIATE_CONNECTIONS)
    public void bindOutsideSuw_dontRegisterUsbReceiver() {
        // Set USER_SETUP_COMPLETE = 1 to simulate running outside of SUW.
        Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 1,
                mMockUserState.mUserId);

        mConnection.bindLocked();

        verify(mMockContext, never()).registerReceiver(any(), any());
    }

    @Test
    @EnableFlags(
            com.android.server.accessibility.Flags.FLAG_ENABLE_BRAILLE_SUW_IMMEDIATE_CONNECTIONS)
    public void sendUsbAttachedIntentInSuw_grantPermissionToUsbDevice() {
        // Set USER_SETUP_COMPLETE = 0 to simulate running in SUW.
        Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 0,
                mMockUserState.mUserId);

        mConnection.bindLocked();

        // Get the registered UsbDeviceReceiver.
        ArgumentCaptor<BroadcastReceiver> usbDeviceReceiverCaptor = ArgumentCaptor.forClass(
                BroadcastReceiver.class);
        verify(mMockContext).registerReceiver(usbDeviceReceiverCaptor.capture(), any());

        // Setup USB device attach intent.
        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
        intent.putExtra(UsbManager.EXTRA_DEVICE, usbDevice);

        int expectedUid = 74859;
        mMockResolveInfo.serviceInfo.applicationInfo.uid = expectedUid;

        // Send intent and verify permission is granted to the USB device.
        UsbManager usbManager = Mockito.mock(UsbManager.class);
        when(mMockContext.getSystemService(Context.USB_SERVICE)).thenReturn(usbManager);
        when(mMockContext.getSystemServiceName(UsbManager.class)).thenReturn(Context.USB_SERVICE);
        usbDeviceReceiverCaptor.getValue().onReceive(mMockContext, intent);
        verify(usbManager).grantPermission(usbDevice, expectedUid);
    }

    @Test
    @EnableFlags(
            com.android.server.accessibility.Flags.FLAG_ENABLE_BRAILLE_SUW_IMMEDIATE_CONNECTIONS)
    public void sendUsbAttachedIntentOutsideSuw_doNotGrantPermissionToUsbDevice() {
        // Set USER_SETUP_COMPLETE = 0 to simulate running in SUW.
        Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 0,
                mMockUserState.mUserId);

        mConnection.bindLocked();

        // After registering the USB receiver in SUW, set USER_SETUP_COMPLETE = 1 to simulate
        // plugging in the USB device outside of SUW.
        Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 1,
                mMockUserState.mUserId);

        // Get the registered UsbDeviceReceiver.
        ArgumentCaptor<BroadcastReceiver> usbDeviceReceiverCaptor = ArgumentCaptor.forClass(
                BroadcastReceiver.class);
        verify(mMockContext).registerReceiver(usbDeviceReceiverCaptor.capture(), any());

        // Setup USB device attach intent.
        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
        intent.putExtra(UsbManager.EXTRA_DEVICE, usbDevice);

        // Send intent and verify permission isn't granted to the USB device because it's outside
        // the SUW.
        UsbManager usbManager = Mockito.mock(UsbManager.class);
        when(mMockContext.getSystemService(Context.USB_SERVICE)).thenReturn(usbManager);
        when(mMockContext.getSystemServiceName(UsbManager.class)).thenReturn(Context.USB_SERVICE);
        usbDeviceReceiverCaptor.getValue().onReceive(mMockContext, intent);
        verify(usbManager, never()).grantPermission(any(), any());
    }
}