Merge "API on AccessibilityService side to support customized system actions."

This commit is contained in:
Hongming Jin
2020-01-17 19:09:11 +00:00
committed by Android (Google) Code Review
11 changed files with 325 additions and 31 deletions

View File

@@ -2854,6 +2854,7 @@ package android.accessibilityservice {
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method @NonNull public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method @NonNull public final java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method @NonNull public final android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -2863,6 +2864,7 @@ package android.accessibilityservice {
method public abstract void onInterrupt();
method protected boolean onKeyEvent(android.view.KeyEvent);
method protected void onServiceConnected();
method public void onSystemActionsChanged();
method public final boolean performGlobalAction(int);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>);

View File

@@ -48,6 +48,7 @@ import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.os.HandlerCaller;
@@ -56,6 +57,7 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -392,7 +394,8 @@ public abstract class AccessibilityService extends Service {
private static final String LOG_TAG = "AccessibilityService";
/**
* Interface used by IAccessibilityServiceWrapper to call the service from its main thread.
* Interface used by IAccessibilityServiceClientWrapper to call the service from its main
* thread.
* @hide
*/
public interface Callbacks {
@@ -413,6 +416,8 @@ public abstract class AccessibilityService extends Service {
/** Accessbility button clicked callbacks for different displays */
void onAccessibilityButtonClicked(int displayId);
void onAccessibilityButtonAvailabilityChanged(boolean available);
/** This is called when the system action list is changed. */
void onSystemActionsChanged();
}
/**
@@ -1643,6 +1648,29 @@ public abstract class AccessibilityService extends Service {
available);
}
/** This is called when the system action list is changed. */
public void onSystemActionsChanged() {
}
/**
* Returns a list of system actions available in the system right now.
*
* @return A list of available system actions.
*/
public final @NonNull List<AccessibilityAction> getSystemActions() {
IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
if (connection != null) {
try {
return connection.getSystemActions();
} catch (RemoteException re) {
Log.w(LOG_TAG, "Error while calling getSystemActions", re);
re.rethrowFromSystemServer();
}
}
return Collections.emptyList();
}
/**
* Performs a global action. Such an action can be performed
* at any moment regardless of the current application or user
@@ -1894,6 +1922,11 @@ public abstract class AccessibilityService extends Service {
public void onAccessibilityButtonAvailabilityChanged(boolean available) {
AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
}
@Override
public void onSystemActionsChanged() {
AccessibilityService.this.onSystemActionsChanged();
}
});
}
@@ -1918,6 +1951,7 @@ public abstract class AccessibilityService extends Service {
private static final int DO_ON_FINGERPRINT_GESTURE = 11;
private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12;
private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13;
private static final int DO_ON_SYSTEM_ACTIONS_CHANGED = 14;
private final HandlerCaller mCaller;
@@ -2014,6 +2048,11 @@ public abstract class AccessibilityService extends Service {
mCaller.sendMessage(message);
}
/** This is called when the system action list is changed. */
public void onSystemActionsChanged() {
mCaller.sendMessage(mCaller.obtainMessage(DO_ON_SYSTEM_ACTIONS_CHANGED));
}
@Override
public void executeMessage(Message message) {
switch (message.what) {
@@ -2035,14 +2074,14 @@ public abstract class AccessibilityService extends Service {
/* ignore - best effort */
}
}
} return;
return;
}
case DO_ON_INTERRUPT: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
mCallback.onInterrupt();
}
} return;
return;
}
case DO_INIT: {
mConnectionId = message.arg1;
SomeArgs args = (SomeArgs) message.obj;
@@ -2062,18 +2101,18 @@ public abstract class AccessibilityService extends Service {
AccessibilityInteractionClient.getInstance().clearCache();
mCallback.init(AccessibilityInteractionClient.NO_ID, null);
}
} return;
return;
}
case DO_ON_GESTURE: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
mCallback.onGesture((AccessibilityGestureEvent) message.obj);
}
} return;
return;
}
case DO_CLEAR_ACCESSIBILITY_CACHE: {
AccessibilityInteractionClient.getInstance().clearCache();
} return;
return;
}
case DO_ON_KEY_EVENT: {
KeyEvent event = (KeyEvent) message.obj;
try {
@@ -2096,8 +2135,8 @@ public abstract class AccessibilityService extends Service {
/* ignore - best effort */
}
}
} return;
return;
}
case DO_ON_MAGNIFICATION_CHANGED: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
final SomeArgs args = (SomeArgs) message.obj;
@@ -2110,45 +2149,53 @@ public abstract class AccessibilityService extends Service {
mCallback.onMagnificationChanged(displayId, region, scale,
centerX, centerY);
}
} return;
return;
}
case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
final int showMode = (int) message.arg1;
mCallback.onSoftKeyboardShowModeChanged(showMode);
}
} return;
return;
}
case DO_GESTURE_COMPLETE: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
final boolean successfully = message.arg2 == 1;
mCallback.onPerformGestureResult(message.arg1, successfully);
}
} return;
return;
}
case DO_ON_FINGERPRINT_ACTIVE_CHANGED: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1);
}
} return;
return;
}
case DO_ON_FINGERPRINT_GESTURE: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
mCallback.onFingerprintGesture(message.arg1);
}
} return;
case (DO_ACCESSIBILITY_BUTTON_CLICKED): {
return;
}
case DO_ACCESSIBILITY_BUTTON_CLICKED: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
mCallback.onAccessibilityButtonClicked(message.arg1);
}
} return;
case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): {
return;
}
case DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
final boolean available = (message.arg1 != 0);
mCallback.onAccessibilityButtonAvailabilityChanged(available);
}
} return;
return;
}
case DO_ON_SYSTEM_ACTIONS_CHANGED: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
mCallback.onSystemActionsChanged();
}
return;
}
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}

View File

@@ -55,4 +55,6 @@ import android.view.KeyEvent;
void onAccessibilityButtonClicked(int displayId);
void onAccessibilityButtonAvailabilityChanged(boolean available);
void onSystemActionsChanged();
}

View File

@@ -25,8 +25,10 @@ import android.os.RemoteCallback;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.accessibility.AccessibilityWindowInfo;
import java.util.List;
/**
* Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -67,6 +69,7 @@ interface IAccessibilityServiceConnection {
AccessibilityServiceInfo getServiceInfo();
boolean performGlobalAction(int action);
List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
void disableSelf();

View File

@@ -1253,6 +1253,11 @@ public final class UiAutomation {
/* do nothing */
}
@Override
public void onSystemActionsChanged() {
/* do nothing */
}
@Override
public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
/* do nothing */

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2020 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 android.accessibilityservice;
import static org.mockito.Mockito.verify;
import android.content.Intent;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.view.accessibility.AccessibilityEvent;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Unit tests for AccessibilityService.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AccessibilityServiceTest {
private static final String TAG = "AccessibilityServiceTest";
private static final int CONNECTION_ID = 1;
private static class AccessibilityServiceTestClass extends AccessibilityService {
private IAccessibilityServiceClient mCallback;
private Looper mLooper;
AccessibilityServiceTestClass() {
super();
attachBaseContext(InstrumentationRegistry.getContext());
mLooper = InstrumentationRegistry.getContext().getMainLooper();
}
public void setupCallback(IAccessibilityServiceClient callback) {
mCallback = callback;
}
public Looper getMainLooper() {
return mLooper;
}
public void onAccessibilityEvent(AccessibilityEvent event) { }
public void onInterrupt() { }
@Override
public void onSystemActionsChanged() {
try {
if (mCallback != null) mCallback.onSystemActionsChanged();
} catch (RemoteException e) {
}
}
}
private @Mock IAccessibilityServiceClient mMockClientForCallback;
private @Mock IAccessibilityServiceConnection mMockConnection;
private @Mock IBinder mMockIBinder;
private IAccessibilityServiceClient mServiceInterface;
private AccessibilityServiceTestClass mService;
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mService = new AccessibilityServiceTestClass();
mService.setupCallback(mMockClientForCallback);
mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent());
mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder);
}
@Test
public void testOnSystemActionsChanged() throws RemoteException {
mServiceInterface.onSystemActionsChanged();
verify(mMockClientForCallback).onSystemActionsChanged();
}
@Test
public void testGetSystemActions() throws RemoteException {
mService.getSystemActions();
verify(mMockConnection).getSystemActions();
}
}

View File

@@ -25,6 +25,9 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteCallback;
import java.util.Collections;
import java.util.List;
/**
* Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement
* all of the methods
@@ -85,6 +88,10 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon
return false;
}
public List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() {
return Collections.emptyList();
}
public void disableSelf() {}
public void setOnKeyEventResult(boolean handled, int sequence) {}

View File

@@ -80,6 +80,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -774,6 +775,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return mSystemActionPerformer.performSystemAction(action);
}
@Override
public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() {
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return Collections.emptyList();
}
}
return mSystemActionPerformer.getSystemActions();
}
@Override
public boolean isFingerprintGestureDetectionAvailable() {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
@@ -1252,6 +1263,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
gestureEvent).sendToTarget();
}
public void notifySystemActionsChangedLocked() {
mInvocationHandler.sendEmptyMessage(
InvocationHandler.MSG_ON_SYSTEM_ACTIONS_CHANGED);
}
public void notifyClearAccessibilityNodeInfoCache() {
mInvocationHandler.sendEmptyMessage(
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
@@ -1350,6 +1366,18 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
}
private void notifySystemActionsChangedInternal() {
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
listener.onSystemActionsChanged();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error sending system actions change to " + mService,
re);
}
}
}
private void notifyClearAccessibilityCacheInternal() {
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
@@ -1544,6 +1572,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
private static final int MSG_ON_SYSTEM_ACTIONS_CHANGED = 9;
/** List of magnification callback states, mapping from displayId -> Boolean */
@GuardedBy("mlock")
@@ -1591,7 +1620,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final boolean available = (message.arg1 != 0);
notifyAccessibilityButtonAvailabilityChangedInternal(available);
} break;
case MSG_ON_SYSTEM_ACTIONS_CHANGED: {
notifySystemActionsChangedInternal();
break;
}
default: {
throw new IllegalArgumentException("Unknown message: " + type);
}

View File

@@ -137,7 +137,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements AbstractAccessibilityServiceConnection.SystemSupport,
AccessibilityUserState.ServiceInfoChangeListener,
AccessibilityWindowManager.AccessibilityEventSender,
AccessibilitySecurityPolicy.AccessibilityUserManager {
AccessibilitySecurityPolicy.AccessibilityUserManager,
SystemActionPerformer.SystemActionsChangedListener {
private static final boolean DEBUG = false;
@@ -294,7 +295,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManager = mContext.getPackageManager();
mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService);
mSystemActionPerformer =
new SystemActionPerformer(mContext, mWindowManagerService, null, this);
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
mWindowManagerService, this, mSecurityPolicy, this);
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
@@ -904,6 +906,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
/**
* Called when the system action list is changed.
*/
@Override
public void onSystemActionsChanged() {
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
notifySystemActionsChangedLocked(state);
}
}
@VisibleForTesting
void notifySystemActionsChangedLocked(AccessibilityUserState userState) {
for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = userState.mBoundServices.get(i);
service.notifySystemActionsChangedLocked();
}
}
@VisibleForTesting
public boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
synchronized (mLock) {

View File

@@ -494,6 +494,13 @@ public class AbstractAccessibilityServiceConnectionTest {
verify(mMockSystemActionPerformer).performSystemAction(GLOBAL_ACTION_HOME);
}
@Test
public void getSystemActions() {
List<AccessibilityNodeInfo.AccessibilityAction> actions =
mServiceConnection.getSystemActions();
verify(mMockSystemActionPerformer).getSystemActions();
}
@Test
public void isFingerprintGestureDetectionAvailable_hasFingerPrintSupport_returnTrue() {
when(mMockFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable())

View File

@@ -16,16 +16,28 @@
package com.android.server.accessibility;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Icon;
import android.os.IBinder;
import android.os.UserHandle;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -34,6 +46,7 @@ import androidx.test.InstrumentationRegistry;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -59,6 +72,14 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
new AccessibilityAction(ACTION_ID, LABEL);
static final ComponentName COMPONENT_NAME = new ComponentName(
"com.android.server.accessibility", "AccessibilityManagerServiceTest");
static final int SERVICE_ID = 42;
@Mock private Context mMockContext;
@Mock private AccessibilityServiceInfo mMockServiceInfo;
@Mock private ResolveInfo mMockResolveInfo;
@Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock private PackageManager mMockPackageManager;
@Mock private WindowManagerInternal mMockWindowManagerService;
@Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
@@ -66,7 +87,12 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
@Mock private AccessibilityWindowManager mMockA11yWindowManager;
@Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
@Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
@Mock private IBinder mMockBinder;
@Mock private IAccessibilityServiceClient mMockServiceClient;
private AccessibilityUserState mUserState;
private MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
private AccessibilityServiceConnection mAccessibilityServiceConnection;
private AccessibilityManagerService mA11yms;
@Override
@@ -74,9 +100,11 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
mA11yms = new AccessibilityManagerService(
InstrumentationRegistry.getContext(),
mMockPackageManager,
@@ -86,6 +114,35 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
mMockA11yDisplayListener);
}
private void setupAccessibilityServiceConnection() {
when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(
InstrumentationRegistry.getContext().getSystemService(
Context.DISPLAY_SERVICE));
mUserState = new AccessibilityUserState(UserHandle.USER_SYSTEM, mMockContext, mA11yms);
when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
mAccessibilityServiceConnection = new AccessibilityServiceConnection(
mUserState,
mMockContext,
COMPONENT_NAME,
mMockServiceInfo,
SERVICE_ID,
mHandler,
new Object(),
mMockSecurityPolicy,
mMockSystemSupport,
mMockWindowManagerService,
mMockSystemActionPerformer,
mMockA11yWindowManager,
mMockActivityTaskManagerInternal);
mAccessibilityServiceConnection.bindLocked();
mAccessibilityServiceConnection.onServiceConnected(COMPONENT_NAME, mMockBinder);
}
@SmallTest
public void testRegisterSystemActionWithoutPermission() throws Exception {
doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
@@ -125,4 +182,11 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
mA11yms.unregisterSystemAction(ACTION_ID);
verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
}
@SmallTest
public void testOnSystemActionsChanged() throws Exception {
setupAccessibilityServiceConnection();
mA11yms.notifySystemActionsChangedLocked(mUserState);
verify(mMockServiceClient).onSystemActionsChanged();
}
}