System action registration API for SystemUI

Bug: 136286274
Test: atest AccessibilityManagerTest
      atest AccessibilityManagerServiceTest
Change-Id: I6bbdf3627bfc9b39551cc7809dda1cf43d1d6ea4
This commit is contained in:
Hongming Jin
2019-12-10 10:32:58 -08:00
parent 82825a24f5
commit bc76112e0b
19 changed files with 313 additions and 16 deletions

View File

@@ -11495,6 +11495,8 @@ package android.view.accessibility {
public final class AccessibilityManager {
method public int getAccessibilityWindowId(@Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
}
}

View File

@@ -31,6 +31,7 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.RemoteAction;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -1208,6 +1209,61 @@ public final class AccessibilityManager {
}
}
/**
* Register the provided {@link RemoteAction} with the given actionId
*
* @param action The remote action to be registered with the given actionId as system action.
* @param actionId The id uniquely identify the system action.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
public void registerSystemAction(@NonNull RemoteAction action, int actionId) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
if (service == null) {
return;
}
}
try {
service.registerSystemAction(action, actionId);
if (DEBUG) {
Log.i(LOG_TAG, "System action " + action.getTitle() + " is registered.");
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error registering system action " + action.getTitle() + " ", re);
}
}
/**
* Unregister a system action with the given actionId
*
* @param actionId The id uniquely identify the system action to be unregistered.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
public void unregisterSystemAction(int actionId) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
if (service == null) {
return;
}
}
try {
service.unregisterSystemAction(actionId);
if (DEBUG) {
Log.i(LOG_TAG, "System action with actionId " + actionId + " is unregistered.");
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error unregistering system action with actionId " + actionId + " ", re);
}
}
/**
* Notifies that the accessibility button in the system's navigation area has been clicked
*

View File

@@ -16,6 +16,7 @@
package android.view.accessibility;
import android.app.RemoteAction;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.IAccessibilityServiceClient;
@@ -82,4 +83,7 @@ interface IAccessibilityManager {
int getAccessibilityWindowId(IBinder windowToken);
long getRecommendedTimeoutMillis();
oneway void registerSystemAction(in RemoteAction action, int actionId);
oneway void unregisterSystemAction(int actionId);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.server.accessibility;
package android.view.accessibility;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertSame;
@@ -29,16 +29,17 @@ import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.IntPair;
import com.android.server.accessibility.test.MessageCapturingHandler;
import org.junit.After;
import org.junit.Before;
@@ -57,6 +58,16 @@ import java.util.List;
public class AccessibilityManagerTest {
private static final boolean WITH_A11Y_ENABLED = true;
private static final boolean WITH_A11Y_DISABLED = false;
private static final String LABEL = "label";
private static final String INTENT_ACTION = "TESTACTION";
private static final String DESCRIPTION = "description";
private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
private static final RemoteAction TEST_ACTION = new RemoteAction(
Icon.createWithContentUri("content://test"),
LABEL,
DESCRIPTION,
TEST_PENDING_INTENT);
@Mock private IAccessibilityManager mMockService;
private MessageCapturingHandler mHandler;
@@ -121,6 +132,29 @@ public class AccessibilityManagerTest {
verify(mMockService).interrupt(UserHandle.USER_CURRENT);
}
@Test
public void testRegisterSystemAction() throws Exception {
AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
RemoteAction action = new RemoteAction(
Icon.createWithContentUri("content://test"),
LABEL,
DESCRIPTION,
TEST_PENDING_INTENT);
final int actionId = 0;
manager.registerSystemAction(TEST_ACTION, actionId);
verify(mMockService).registerSystemAction(TEST_ACTION, actionId);
}
@Test
public void testUnregisterSystemAction() throws Exception {
AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
final int actionId = 0;
manager.unregisterSystemAction(actionId);
verify(mMockService).unregisterSystemAction(actionId);
}
@Test
public void testIsEnabled() throws Exception {
// Create manager with a11y enabled

View File

@@ -35,6 +35,7 @@ import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.appwidget.AppWidgetManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -148,6 +149,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// their capabilities are ready.
private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000;
static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction";
static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction";
private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
"registerUiTestAutomationService";
@@ -253,6 +256,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
@VisibleForTesting
AccessibilityManagerService(
Context context,
PackageManager packageManager,
AccessibilitySecurityPolicy securityPolicy,
SystemActionPerformer systemActionPerformer,
AccessibilityWindowManager a11yWindowManager,
AccessibilityDisplayListener a11yDisplayListener) {
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
mMainHandler = new MainHandler(mContext.getMainLooper());
mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManager = packageManager;
mSecurityPolicy = securityPolicy;
mSystemActionPerformer = systemActionPerformer;
mA11yWindowManager = a11yWindowManager;
mA11yDisplayListener = a11yDisplayListener;
init();
}
/**
* Creates a new instance.
*
@@ -260,21 +284,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
public AccessibilityManagerService(Context context) {
mContext = context;
mPackageManager = mContext.getPackageManager();
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
mMainHandler = new MainHandler(mContext.getMainLooper());
mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManager = mContext.getPackageManager();
mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService);
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
mWindowManagerService, this, mSecurityPolicy, this);
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
init();
}
private void init() {
mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
context.getContentResolver());
mContext.getContentResolver());
}
@Override
@@ -623,6 +650,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
event.recycle();
}
/**
* This is the implementation of AccessibilityManager system API.
* System UI calls into this method through AccessibilityManager system API to register a
* system action.
*/
@Override
public void registerSystemAction(RemoteAction action, int actionId) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
FUNCTION_REGISTER_SYSTEM_ACTION);
mSystemActionPerformer.registerSystemAction(actionId, action);
}
/**
* This is the implementation of AccessibilityManager system API.
* System UI calls into this method through AccessibilityManager system API to unregister a
* system action.
*/
@Override
public void unregisterSystemAction(int actionId) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
FUNCTION_UNREGISTER_SYSTEM_ACTION);
mSystemActionPerformer.unregisterSystemAction(actionId);
}
@Override
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
synchronized (mLock) {

View File

@@ -157,8 +157,13 @@ public class SystemActionPerformer {
/**
* This method is called to register a system action. If a system action is already registered
* with the given id, the existing system action will be overwritten.
*
* This method is supposed to be package internal since this class is meant to be used by
* AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
* to be mocked.
*/
void registerSystemAction(int id, RemoteAction action) {
@VisibleForTesting
public void registerSystemAction(int id, RemoteAction action) {
synchronized (mSystemActionLock) {
mRegisteredSystemActions.put(id, action);
}
@@ -170,8 +175,13 @@ public class SystemActionPerformer {
/**
* This method is called to unregister a system action previously registered through
* registerSystemAction.
*
* This method is supposed to be package internal since this class is meant to be used by
* AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
* to be mocked.
*/
void unregisterSystemAction(int id) {
@VisibleForTesting
public void unregisterSystemAction(int id) {
synchronized (mSystemActionLock) {
mRegisteredSystemActions.remove(id);
}

View File

@@ -85,6 +85,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import org.junit.Before;

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.accessibility;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.Manifest;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import androidx.test.InstrumentationRegistry;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* APCT tests for {@link AccessibilityManagerService}.
*/
public class AccessibilityManagerServiceTest extends AndroidTestCase {
private static final String TAG = "A11Y_MANAGER_SERVICE_TEST";
private static final int ACTION_ID = 20;
private static final String LABEL = "label";
private static final String INTENT_ACTION = "TESTACTION";
private static final String DESCRIPTION = "description";
private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
private static final RemoteAction TEST_ACTION = new RemoteAction(
Icon.createWithContentUri("content://test"),
LABEL,
DESCRIPTION,
TEST_PENDING_INTENT);
private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
new AccessibilityAction(ACTION_ID, LABEL);
@Mock private PackageManager mMockPackageManager;
@Mock private WindowManagerInternal mMockWindowManagerService;
@Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
@Mock private SystemActionPerformer mMockSystemActionPerformer;
@Mock private AccessibilityWindowManager mMockA11yWindowManager;
@Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
@Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
private AccessibilityManagerService mA11yms;
@Override
protected void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
mA11yms = new AccessibilityManagerService(
InstrumentationRegistry.getContext(),
mMockPackageManager,
mMockSecurityPolicy,
mMockSystemActionPerformer,
mMockA11yWindowManager,
mMockA11yDisplayListener);
}
@SmallTest
public void testRegisterSystemActionWithoutPermission() throws Exception {
doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
Manifest.permission.MANAGE_ACCESSIBILITY,
AccessibilityManagerService.FUNCTION_REGISTER_SYSTEM_ACTION);
try {
mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
fail();
} catch (SecurityException expected) {
}
verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
}
@SmallTest
public void testRegisterSystemAction() throws Exception {
mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
}
@SmallTest
public void testUnregisterSystemActionWithoutPermission() throws Exception {
doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
Manifest.permission.MANAGE_ACCESSIBILITY,
AccessibilityManagerService.FUNCTION_UNREGISTER_SYSTEM_ACTION);
try {
mA11yms.unregisterSystemAction(ACTION_ID);
fail();
} catch (SecurityException expected) {
}
verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
}
@SmallTest
public void testUnregisterSystemAction() throws Exception {
mA11yms.unregisterSystemAction(ACTION_ID);
verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
}
}

View File

@@ -44,6 +44,7 @@ import android.os.UserHandle;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;

View File

@@ -53,6 +53,7 @@ import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;

View File

@@ -31,6 +31,8 @@ import android.accessibilityservice.FingerprintGestureController;
import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
import android.accessibilityservice.IAccessibilityServiceConnection;
import com.android.server.accessibility.test.MessageCapturingHandler;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;

View File

@@ -31,6 +31,7 @@ import android.hardware.fingerprint.IFingerprintService;
import android.view.KeyEvent;
import com.android.server.accessibility.FingerprintGestureDispatcher.FingerprintGestureClient;
import com.android.server.accessibility.test.MessageCapturingHandler;
import org.junit.After;
import org.junit.Before;

View File

@@ -44,6 +44,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.KeyEventDispatcher.KeyEventFilter;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.policy.WindowManagerPolicy;
import org.hamcrest.Description;

View File

@@ -33,6 +33,7 @@ import android.view.KeyEvent;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.policy.WindowManagerPolicy;
import org.hamcrest.Description;
@@ -212,4 +213,4 @@ public class KeyboardInterceptorTest {
description.appendText("Matches key event");
}
}
}
}

View File

@@ -47,6 +47,7 @@ import android.view.MagnificationSpec;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;

View File

@@ -54,6 +54,7 @@ import android.view.accessibility.AccessibilityEvent;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.accessibility.utils.MotionEventMatcher;
import org.hamcrest.Description;

View File

@@ -37,6 +37,7 @@ import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.view.accessibility.AccessibilityEvent;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;

View File

@@ -22,6 +22,7 @@ java_library {
static_libs: [
"junit",
"hamcrest-library",
"androidx.test.runner",
],
libs: [

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.server.accessibility;
package com.android.server.accessibility.test;
import android.os.Handler;
import android.os.Looper;
@@ -31,7 +31,7 @@ import java.util.List;
* at their target.
*/
public class MessageCapturingHandler extends Handler {
List<Pair<Message, Long>> timedMessages = new ArrayList<>();
public List<Pair<Message, Long>> timedMessages = new ArrayList<>();
Handler.Callback mCallback;