Merge "Prompt notifications for non-accessibility services" into sc-dev
This commit is contained in:
@@ -7036,6 +7036,15 @@ public final class Settings {
|
||||
public static final String ENABLED_ACCESSIBILITY_SERVICES =
|
||||
"enabled_accessibility_services";
|
||||
|
||||
/**
|
||||
* List of the notified non-accessibility category accessibility services.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Readable
|
||||
public static final String NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES =
|
||||
"notified_non_accessibility_category_services";
|
||||
|
||||
/**
|
||||
* List of the accessibility services to which the user has granted
|
||||
* permission to put the device into touch exploration mode.
|
||||
|
||||
@@ -58,6 +58,7 @@ public class SystemNotificationChannels {
|
||||
public static String SYSTEM_CHANGES = "SYSTEM_CHANGES";
|
||||
public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
|
||||
public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
|
||||
public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
|
||||
|
||||
public static void createAll(Context context) {
|
||||
final NotificationManager nm = context.getSystemService(NotificationManager.class);
|
||||
@@ -199,6 +200,12 @@ public class SystemNotificationChannels {
|
||||
newFeaturePrompt.setBlockable(true);
|
||||
channelsList.add(newFeaturePrompt);
|
||||
|
||||
final NotificationChannel accessibilitySecurityPolicyChannel = new NotificationChannel(
|
||||
ACCESSIBILITY_SECURITY_POLICY,
|
||||
context.getString(R.string.notification_channel_accessibility_security_policy),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
channelsList.add(accessibilitySecurityPolicyChannel);
|
||||
|
||||
nm.createNotificationChannels(channelsList);
|
||||
}
|
||||
|
||||
|
||||
27
core/res/res/drawable/ic_accessibility_24dp.xml
Normal file
27
core/res/res/drawable/ic_accessibility_24dp.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2021 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="21dp"
|
||||
android:height="21dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1S6.11,6.7 3.5,6L3,8c1.86,0.5 4,0.83 6,
|
||||
1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1L20.5,6zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2s-2,
|
||||
0.9 -2,2S10.9,6 12,6z"
|
||||
android:fillColor="#FF000000"/>
|
||||
</vector>
|
||||
@@ -743,6 +743,10 @@
|
||||
magnification. [CHAR_LIMIT=NONE]-->
|
||||
<string name="notification_channel_accessibility_magnification">Magnification</string>
|
||||
|
||||
<!-- Text shown when viewing channel settings for notifications related to accessibility
|
||||
security policy. [CHAR_LIMIT=NONE]-->
|
||||
<string name="notification_channel_accessibility_security_policy">Accessibility security policy</string>
|
||||
|
||||
<!-- Label for foreground service notification when one app is running.
|
||||
[CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] -->
|
||||
<string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is
|
||||
@@ -5910,4 +5914,9 @@ ul.</string>
|
||||
<string name="splash_screen_view_icon_description">Application icon</string>
|
||||
<!-- Content description for the branding image on the splash screen. [CHAR LIMIT=50] -->
|
||||
<string name="splash_screen_view_branding_description">Application branding image</string>
|
||||
|
||||
<!-- Notification title to prompt the user that some accessibility service has view and control access. [CHAR LIMIT=50] -->
|
||||
<string name="view_and_control_notification_title">Check access settings</string>
|
||||
<!-- Notification content to prompt the user that some accessibility service has view and control access. [CHAR LIMIT=none] -->
|
||||
<string name="view_and_control_notification_content"><xliff:g id="service_name" example="TalkBack">%s</xliff:g> can view and control your screen. Tap to review.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3541,6 +3541,7 @@
|
||||
<java-symbol type="string" name="notification_channel_system_changes" />
|
||||
<java-symbol type="string" name="notification_channel_do_not_disturb" />
|
||||
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
|
||||
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
|
||||
<java-symbol type="string" name="config_defaultAutofillService" />
|
||||
<java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
|
||||
<java-symbol type="string" name="config_defaultTextClassifierPackage" />
|
||||
@@ -4323,4 +4324,9 @@
|
||||
<java-symbol type="id" name="remote_views_next_child" />
|
||||
<java-symbol type="id" name="remote_views_stable_id" />
|
||||
<java-symbol type="id" name="remote_views_override_id" />
|
||||
|
||||
<!-- View and control prompt -->
|
||||
<java-symbol type="drawable" name="ic_accessibility_24dp" />
|
||||
<java-symbol type="string" name="view_and_control_notification_title" />
|
||||
<java-symbol type="string" name="view_and_control_notification_content" />
|
||||
</resources>
|
||||
|
||||
@@ -340,5 +340,9 @@ message SystemMessage {
|
||||
// Notify the user that window magnification is available.
|
||||
// package: android
|
||||
NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE = 1004;
|
||||
|
||||
// Notify the user that some accessibility service has view and control permissions.
|
||||
// package: android
|
||||
NOTE_A11Y_VIEW_AND_CONTROL_ACCESS = 1005;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,13 +309,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
*/
|
||||
public AccessibilityManagerService(Context context) {
|
||||
mContext = context;
|
||||
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
||||
mPowerManager = context.getSystemService(PowerManager.class);
|
||||
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
|
||||
mA11yController = mWindowManagerService.getAccessibilityController();
|
||||
mMainHandler = new MainHandler(mContext.getMainLooper());
|
||||
mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
|
||||
PolicyWarningUIController policyWarningUIController;
|
||||
if (AccessibilitySecurityPolicy.POLICY_WARNING_ENABLED) {
|
||||
policyWarningUIController = new PolicyWarningUIController(mMainHandler, context,
|
||||
new PolicyWarningUIController.NotificationController(context));
|
||||
}
|
||||
mSecurityPolicy = new AccessibilitySecurityPolicy(policyWarningUIController, mContext,
|
||||
this);
|
||||
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
|
||||
mWindowManagerService, this, mSecurityPolicy, this);
|
||||
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
|
||||
@@ -351,6 +357,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
if (isA11yTracingEnabled()) {
|
||||
logTrace(LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState);
|
||||
}
|
||||
mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId,
|
||||
userState.mBoundServices);
|
||||
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
|
||||
}
|
||||
|
||||
@@ -1302,6 +1310,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
AccessibilityUserState userState = getCurrentUserStateLocked();
|
||||
|
||||
readConfigurationForUserStateLocked(userState);
|
||||
mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
|
||||
// Even if reading did not yield change, we have to update
|
||||
// the state since the context in which the current user
|
||||
// state was used has changed since it was inactive.
|
||||
@@ -3665,6 +3674,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
}
|
||||
} else if (mEnabledAccessibilityServicesUri.equals(uri)) {
|
||||
if (readEnabledAccessibilityServicesLocked(userState)) {
|
||||
mSecurityPolicy.onEnabledServicesChangedLocked(userState.mUserId,
|
||||
userState.mEnabledServices);
|
||||
userState.updateCrashedServicesIfNeededLocked();
|
||||
onUserStateChangedLocked(userState);
|
||||
}
|
||||
|
||||
@@ -41,21 +41,19 @@ import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class provides APIs of accessibility security policies for accessibility manager
|
||||
* to grant accessibility capabilities or events access right to accessibility service.
|
||||
* to grant accessibility capabilities or events access right to accessibility services. And also
|
||||
* monitors the current bound accessibility services to prompt permission warnings for
|
||||
* not accessibility-categorized ones.
|
||||
*/
|
||||
public class AccessibilitySecurityPolicy {
|
||||
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
|
||||
private static final String LOG_TAG = "AccessibilitySecurityPolicy";
|
||||
|
||||
private final Context mContext;
|
||||
private final PackageManager mPackageManager;
|
||||
private final UserManager mUserManager;
|
||||
private final AppOpsManager mAppOpsManager;
|
||||
|
||||
private AppWidgetManagerInternal mAppWidgetService;
|
||||
|
||||
private static final int KEEP_SOURCE_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED
|
||||
| AccessibilityEvent.TYPE_VIEW_FOCUSED
|
||||
| AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
|
||||
@@ -72,6 +70,8 @@ public class AccessibilitySecurityPolicy {
|
||||
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
|
||||
| AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
|
||||
|
||||
public static final boolean POLICY_WARNING_ENABLED = true;
|
||||
|
||||
/**
|
||||
* Methods that should find their way into separate modules, but are still in AMS
|
||||
* TODO (b/111889696): Refactoring UserState to AccessibilityUserManager.
|
||||
@@ -84,19 +84,32 @@ public class AccessibilitySecurityPolicy {
|
||||
// TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private final PackageManager mPackageManager;
|
||||
private final UserManager mUserManager;
|
||||
private final AppOpsManager mAppOpsManager;
|
||||
private final AccessibilityUserManager mAccessibilityUserManager;
|
||||
private final PolicyWarningUIController mPolicyWarningUIController;
|
||||
/** All bound accessibility services which don't belong to accessibility category. */
|
||||
private final ArraySet<ComponentName> mNonA11yCategoryServices = new ArraySet<>();
|
||||
|
||||
private AppWidgetManagerInternal mAppWidgetService;
|
||||
private AccessibilityWindowManager mAccessibilityWindowManager;
|
||||
private int mCurrentUserId = UserHandle.USER_NULL;
|
||||
|
||||
/**
|
||||
* Constructor for AccessibilityManagerService.
|
||||
*/
|
||||
public AccessibilitySecurityPolicy(@NonNull Context context,
|
||||
public AccessibilitySecurityPolicy(PolicyWarningUIController policyWarningUIController,
|
||||
@NonNull Context context,
|
||||
@NonNull AccessibilityUserManager a11yUserManager) {
|
||||
mContext = context;
|
||||
mAccessibilityUserManager = a11yUserManager;
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
|
||||
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
|
||||
mPolicyWarningUIController = policyWarningUIController;
|
||||
mPolicyWarningUIController.setAccessibilityPolicyManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -568,4 +581,98 @@ public class AccessibilitySecurityPolicy {
|
||||
+ permission);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a service was bound or unbound. Checks the current bound accessibility
|
||||
* services and updates alarms.
|
||||
*
|
||||
* @param userId The user id
|
||||
* @param boundServices The bound services
|
||||
*/
|
||||
public void onBoundServicesChangedLocked(int userId,
|
||||
ArrayList<AccessibilityServiceConnection> boundServices) {
|
||||
if (!POLICY_WARNING_ENABLED) {
|
||||
return;
|
||||
}
|
||||
if (mAccessibilityUserManager.getCurrentUserIdLocked() != userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArraySet<ComponentName> tempNonA11yCategoryServices = new ArraySet<>();
|
||||
for (int i = 0; i < boundServices.size(); i++) {
|
||||
final AccessibilityServiceInfo a11yServiceInfo = boundServices.get(
|
||||
i).getServiceInfo();
|
||||
final ComponentName service = a11yServiceInfo.getComponentName().clone();
|
||||
if (!isA11yCategoryService(a11yServiceInfo)) {
|
||||
tempNonA11yCategoryServices.add(service);
|
||||
if (mNonA11yCategoryServices.contains(service)) {
|
||||
mNonA11yCategoryServices.remove(service);
|
||||
} else {
|
||||
mPolicyWarningUIController.onNonA11yCategoryServiceBound(userId, service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < mNonA11yCategoryServices.size(); i++) {
|
||||
final ComponentName service = mNonA11yCategoryServices.valueAt(i);
|
||||
mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(userId, service);
|
||||
}
|
||||
mNonA11yCategoryServices.clear();
|
||||
mNonA11yCategoryServices.addAll(tempNonA11yCategoryServices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after switching to another user. Resets data and cancels old alarms after
|
||||
* switching to another user.
|
||||
*
|
||||
* @param userId The user id
|
||||
* @param enabledServices The enabled services
|
||||
*/
|
||||
public void onSwitchUserLocked(int userId, Set<ComponentName> enabledServices) {
|
||||
if (!POLICY_WARNING_ENABLED) {
|
||||
return;
|
||||
}
|
||||
if (mCurrentUserId == userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPolicyWarningUIController.onSwitchUserLocked(userId, enabledServices);
|
||||
|
||||
for (int i = 0; i < mNonA11yCategoryServices.size(); i++) {
|
||||
mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(mCurrentUserId,
|
||||
mNonA11yCategoryServices.valueAt(i));
|
||||
}
|
||||
mNonA11yCategoryServices.clear();
|
||||
mCurrentUserId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the enabled accessibility services changed.
|
||||
*
|
||||
* @param userId The user id
|
||||
* @param enabledServices The enabled services
|
||||
*/
|
||||
public void onEnabledServicesChangedLocked(int userId,
|
||||
Set<ComponentName> enabledServices) {
|
||||
if (!POLICY_WARNING_ENABLED) {
|
||||
return;
|
||||
}
|
||||
if (mAccessibilityUserManager.getCurrentUserIdLocked() != userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPolicyWarningUIController.onEnabledServicesChangedLocked(userId, enabledServices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies whether the accessibility service is true and designed for accessibility. An
|
||||
* accessibility service is considered as accessibility category if
|
||||
* {@link AccessibilityServiceInfo#isAccessibilityTool} is true.
|
||||
*
|
||||
* @param serviceInfo The accessibility service's serviceInfo.
|
||||
* @return Returns true if it is a true accessibility service.
|
||||
*/
|
||||
public boolean isA11yCategoryService(AccessibilityServiceInfo serviceInfo) {
|
||||
return serviceInfo.isAccessibilityTool();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 android.app.AlarmManager.RTC_WAKEUP;
|
||||
|
||||
import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_VIEW_AND_CONTROL_ACCESS;
|
||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||
|
||||
import android.Manifest;
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.annotation.MainThread;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.StatusBarManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.notification.SystemNotificationChannels;
|
||||
import com.android.internal.util.ImageUtils;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The class handles permission warning notifications for not accessibility-categorized
|
||||
* accessibility services from {@link AccessibilitySecurityPolicy}. And also maintains the setting
|
||||
* {@link Settings.Secure#NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES} in order not to
|
||||
* resend notifications to the same service.
|
||||
*/
|
||||
public class PolicyWarningUIController {
|
||||
private static final String TAG = PolicyWarningUIController.class.getSimpleName();
|
||||
@VisibleForTesting
|
||||
protected static final String ACTION_SEND_NOTIFICATION = TAG + ".ACTION_SEND_NOTIFICATION";
|
||||
@VisibleForTesting
|
||||
protected static final String ACTION_A11Y_SETTINGS = TAG + ".ACTION_A11Y_SETTINGS";
|
||||
@VisibleForTesting
|
||||
protected static final String ACTION_DISMISS_NOTIFICATION =
|
||||
TAG + ".ACTION_DISMISS_NOTIFICATION";
|
||||
private static final int SEND_NOTIFICATION_DELAY_HOURS = 24;
|
||||
|
||||
/** Current enabled accessibility services. */
|
||||
private final ArraySet<ComponentName> mEnabledA11yServices = new ArraySet<>();
|
||||
|
||||
private final Handler mMainHandler;
|
||||
private final AlarmManager mAlarmManager;
|
||||
private final Context mContext;
|
||||
private final NotificationController mNotificationController;
|
||||
|
||||
public PolicyWarningUIController(@NonNull Handler handler, @NonNull Context context,
|
||||
NotificationController notificationController) {
|
||||
mMainHandler = handler;
|
||||
mContext = context;
|
||||
mNotificationController = notificationController;
|
||||
mAlarmManager = mContext.getSystemService(AlarmManager.class);
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_SEND_NOTIFICATION);
|
||||
filter.addAction(ACTION_A11Y_SETTINGS);
|
||||
filter.addAction(ACTION_DISMISS_NOTIFICATION);
|
||||
mContext.registerReceiver(mNotificationController, filter,
|
||||
Manifest.permission.MANAGE_ACCESSIBILITY, mMainHandler);
|
||||
|
||||
}
|
||||
|
||||
protected void setAccessibilityPolicyManager(
|
||||
AccessibilitySecurityPolicy accessibilitySecurityPolicy) {
|
||||
mNotificationController.setAccessibilityPolicyManager(accessibilitySecurityPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates enabled accessibility services and notified accessibility services after switching
|
||||
* to another user.
|
||||
*
|
||||
* @param enabledServices The current enabled services
|
||||
*/
|
||||
public void onSwitchUserLocked(int userId, Set<ComponentName> enabledServices) {
|
||||
mEnabledA11yServices.clear();
|
||||
mEnabledA11yServices.addAll(enabledServices);
|
||||
mMainHandler.sendMessage(obtainMessage(mNotificationController::onSwitchUser, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the newly disabled services and removes its record from the setting
|
||||
* {@link Settings.Secure#NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES} after detecting the
|
||||
* setting {@link Settings.Secure#ENABLED_ACCESSIBILITY_SERVICES} changed.
|
||||
*
|
||||
* @param userId The user id
|
||||
* @param enabledServices The enabled services
|
||||
*/
|
||||
public void onEnabledServicesChangedLocked(int userId,
|
||||
Set<ComponentName> enabledServices) {
|
||||
final ArraySet<ComponentName> disabledServices = new ArraySet<>(mEnabledA11yServices);
|
||||
disabledServices.removeAll(enabledServices);
|
||||
mEnabledA11yServices.clear();
|
||||
mEnabledA11yServices.addAll(enabledServices);
|
||||
mMainHandler.sendMessage(
|
||||
obtainMessage(mNotificationController::onServicesDisabled, userId,
|
||||
disabledServices));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the target service is bound. Sets an 24 hours alarm to the service which is not
|
||||
* notified yet to execute action {@code ACTION_SEND_NOTIFICATION}.
|
||||
*
|
||||
* @param userId The user id
|
||||
* @param service The service's component name
|
||||
*/
|
||||
public void onNonA11yCategoryServiceBound(int userId, ComponentName service) {
|
||||
mMainHandler.sendMessage(obtainMessage(this::setAlarm, userId, service));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the target service is unbound. Cancels the old alarm with intent action
|
||||
* {@code ACTION_SEND_NOTIFICATION} from the service.
|
||||
*
|
||||
* @param userId The user id
|
||||
* @param service The service's component name
|
||||
*/
|
||||
public void onNonA11yCategoryServiceUnbound(int userId, ComponentName service) {
|
||||
mMainHandler.sendMessage(obtainMessage(this::cancelAlarm, userId, service));
|
||||
}
|
||||
|
||||
private void setAlarm(int userId, ComponentName service) {
|
||||
final Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.HOUR, SEND_NOTIFICATION_DELAY_HOURS);
|
||||
mAlarmManager.set(RTC_WAKEUP, cal.getTimeInMillis(),
|
||||
createPendingIntent(mContext, userId, ACTION_SEND_NOTIFICATION,
|
||||
service.flattenToShortString()));
|
||||
}
|
||||
|
||||
private void cancelAlarm(int userId, ComponentName service) {
|
||||
mAlarmManager.cancel(
|
||||
createPendingIntent(mContext, userId, ACTION_SEND_NOTIFICATION,
|
||||
service.flattenToShortString()));
|
||||
}
|
||||
|
||||
protected static PendingIntent createPendingIntent(Context context, int userId, String action,
|
||||
String serviceComponentName) {
|
||||
final Intent intent = new Intent(action);
|
||||
intent.setPackage(context.getPackageName())
|
||||
.setIdentifier(serviceComponentName)
|
||||
.putExtra(Intent.EXTRA_USER_ID, userId);
|
||||
return PendingIntent.getBroadcast(context, 0, intent,
|
||||
PendingIntent.FLAG_IMMUTABLE);
|
||||
}
|
||||
|
||||
/** A sub class to handle notifications and settings on the main thread. */
|
||||
@MainThread
|
||||
public static class NotificationController extends BroadcastReceiver {
|
||||
private static final char RECORD_SEPARATOR = ':';
|
||||
|
||||
/** All accessibility services which are notified to the user by the policy warning rule. */
|
||||
private final ArraySet<ComponentName> mNotifiedA11yServices = new ArraySet<>();
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final Context mContext;
|
||||
|
||||
private int mCurrentUserId;
|
||||
private AccessibilitySecurityPolicy mAccessibilitySecurityPolicy;
|
||||
|
||||
public NotificationController(Context context) {
|
||||
mContext = context;
|
||||
mNotificationManager = mContext.getSystemService(NotificationManager.class);
|
||||
}
|
||||
|
||||
protected void setAccessibilityPolicyManager(
|
||||
AccessibilitySecurityPolicy accessibilitySecurityPolicy) {
|
||||
mAccessibilitySecurityPolicy = accessibilitySecurityPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
final String service = intent.getIdentifier();
|
||||
final ComponentName componentName = ComponentName.unflattenFromString(service);
|
||||
if (TextUtils.isEmpty(action) || TextUtils.isEmpty(service)
|
||||
|| componentName == null) {
|
||||
return;
|
||||
}
|
||||
final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_SYSTEM);
|
||||
if (ACTION_SEND_NOTIFICATION.equals(action)) {
|
||||
trySendNotification(userId, componentName);
|
||||
} else if (ACTION_A11Y_SETTINGS.equals(action)) {
|
||||
launchSettings(userId, componentName);
|
||||
mNotificationManager.cancel(service, NOTE_A11Y_VIEW_AND_CONTROL_ACCESS);
|
||||
onNotificationCanceled(userId, componentName);
|
||||
} else if (ACTION_DISMISS_NOTIFICATION.equals(action)) {
|
||||
onNotificationCanceled(userId, componentName);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onSwitchUser(int userId) {
|
||||
mCurrentUserId = userId;
|
||||
mNotifiedA11yServices.clear();
|
||||
mNotifiedA11yServices.addAll(readNotifiedServiceList(userId));
|
||||
}
|
||||
|
||||
protected void onServicesDisabled(int userId,
|
||||
ArraySet<ComponentName> disabledServices) {
|
||||
if (mNotifiedA11yServices.removeAll(disabledServices)) {
|
||||
writeNotifiedServiceList(userId, mNotifiedA11yServices);
|
||||
}
|
||||
}
|
||||
|
||||
private void trySendNotification(int userId, ComponentName componentName) {
|
||||
if (!AccessibilitySecurityPolicy.POLICY_WARNING_ENABLED) {
|
||||
return;
|
||||
}
|
||||
if (userId != mCurrentUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<AccessibilityServiceInfo> enabledServiceInfos = getEnabledServiceInfos();
|
||||
for (int i = 0; i < enabledServiceInfos.size(); i++) {
|
||||
final AccessibilityServiceInfo a11yServiceInfo = enabledServiceInfos.get(i);
|
||||
if (componentName.flattenToShortString().equals(
|
||||
a11yServiceInfo.getComponentName().flattenToShortString())) {
|
||||
if (!mAccessibilitySecurityPolicy.isA11yCategoryService(a11yServiceInfo)
|
||||
&& !mNotifiedA11yServices.contains(componentName)) {
|
||||
final CharSequence displayName =
|
||||
a11yServiceInfo.getResolveInfo().serviceInfo.loadLabel(
|
||||
mContext.getPackageManager());
|
||||
final Drawable drawable = a11yServiceInfo.getResolveInfo().loadIcon(
|
||||
mContext.getPackageManager());
|
||||
final int size = mContext.getResources().getDimensionPixelSize(
|
||||
android.R.dimen.app_icon_size);
|
||||
sendNotification(userId, componentName.flattenToShortString(),
|
||||
displayName,
|
||||
ImageUtils.buildScaledBitmap(drawable, size, size));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void launchSettings(int userId, ComponentName componentName) {
|
||||
if (userId != mCurrentUserId) {
|
||||
return;
|
||||
}
|
||||
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
intent.putExtra(Intent.EXTRA_COMPONENT_NAME, componentName.flattenToShortString());
|
||||
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(
|
||||
mContext.getDisplayId()).toBundle();
|
||||
mContext.startActivityAsUser(intent, bundle, UserHandle.of(userId));
|
||||
mContext.getSystemService(StatusBarManager.class).collapsePanels();
|
||||
}
|
||||
|
||||
protected void onNotificationCanceled(int userId, ComponentName componentName) {
|
||||
if (userId != mCurrentUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mNotifiedA11yServices.add(componentName)) {
|
||||
writeNotifiedServiceList(userId, mNotifiedA11yServices);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNotification(int userId, String serviceComponentName, CharSequence name,
|
||||
Bitmap bitmap) {
|
||||
final Notification.Builder notificationBuilder = new Notification.Builder(mContext,
|
||||
SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY);
|
||||
notificationBuilder.setSmallIcon(R.drawable.ic_accessibility_24dp)
|
||||
.setContentTitle(
|
||||
mContext.getString(R.string.view_and_control_notification_title))
|
||||
.setContentText(
|
||||
mContext.getString(R.string.view_and_control_notification_content,
|
||||
name))
|
||||
.setStyle(new Notification.BigTextStyle()
|
||||
.bigText(
|
||||
mContext.getString(
|
||||
R.string.view_and_control_notification_content,
|
||||
name)))
|
||||
.setTicker(mContext.getString(R.string.view_and_control_notification_title))
|
||||
.setOnlyAlertOnce(true)
|
||||
.setDeleteIntent(
|
||||
createPendingIntent(mContext, userId, ACTION_DISMISS_NOTIFICATION,
|
||||
serviceComponentName))
|
||||
.setContentIntent(
|
||||
createPendingIntent(mContext, userId, ACTION_A11Y_SETTINGS,
|
||||
serviceComponentName));
|
||||
if (bitmap != null) {
|
||||
notificationBuilder.setLargeIcon(bitmap);
|
||||
}
|
||||
mNotificationManager.notify(serviceComponentName, NOTE_A11Y_VIEW_AND_CONTROL_ACCESS,
|
||||
notificationBuilder.build());
|
||||
}
|
||||
|
||||
private ArraySet<ComponentName> readNotifiedServiceList(int userId) {
|
||||
final String notifiedServiceSetting = Settings.Secure.getStringForUser(
|
||||
mContext.getContentResolver(),
|
||||
Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
|
||||
userId);
|
||||
if (TextUtils.isEmpty(notifiedServiceSetting)) {
|
||||
return new ArraySet<>();
|
||||
}
|
||||
|
||||
final TextUtils.StringSplitter componentNameSplitter =
|
||||
new TextUtils.SimpleStringSplitter(RECORD_SEPARATOR);
|
||||
componentNameSplitter.setString(notifiedServiceSetting);
|
||||
|
||||
final ArraySet<ComponentName> notifiedServices = new ArraySet<>();
|
||||
final Iterator<String> it = componentNameSplitter.iterator();
|
||||
while (it.hasNext()) {
|
||||
final String componentNameString = it.next();
|
||||
final ComponentName notifiedService = ComponentName.unflattenFromString(
|
||||
componentNameString);
|
||||
if (notifiedService != null) {
|
||||
notifiedServices.add(notifiedService);
|
||||
}
|
||||
}
|
||||
return notifiedServices;
|
||||
}
|
||||
|
||||
private void writeNotifiedServiceList(int userId, ArraySet<ComponentName> services) {
|
||||
StringBuilder notifiedServicesBuilder = new StringBuilder();
|
||||
for (int i = 0; i < services.size(); i++) {
|
||||
if (i > 0) {
|
||||
notifiedServicesBuilder.append(RECORD_SEPARATOR);
|
||||
}
|
||||
final ComponentName notifiedService = services.valueAt(i);
|
||||
notifiedServicesBuilder.append(notifiedService.flattenToShortString());
|
||||
}
|
||||
Settings.Secure.putStringForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
|
||||
notifiedServicesBuilder.toString(), userId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected List<AccessibilityServiceInfo> getEnabledServiceInfos() {
|
||||
final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(
|
||||
mContext);
|
||||
return accessibilityManager.getEnabledAccessibilityServiceList(
|
||||
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.accessibility;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
@@ -30,6 +32,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -47,10 +50,13 @@ import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.testing.DexmakerShareClassLoaderRule;
|
||||
import android.testing.TestableContext;
|
||||
import android.util.ArraySet;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityWindowInfo;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -58,7 +64,9 @@ import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -72,9 +80,9 @@ public class AccessibilitySecurityPolicyTest {
|
||||
private static final int APP_UID = 10400;
|
||||
private static final int APP_PID = 2000;
|
||||
private static final int SYSTEM_PID = 558;
|
||||
|
||||
private static final String PERMISSION = "test-permission";
|
||||
private static final String FUNCTION = "test-function-name";
|
||||
private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
|
||||
private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
|
||||
"com.android.server.accessibility", "AccessibilitySecurityPolicyTest");
|
||||
|
||||
private static final int[] ALWAYS_DISPATCH_EVENTS = {
|
||||
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
|
||||
@@ -108,29 +116,51 @@ public class AccessibilitySecurityPolicyTest {
|
||||
|
||||
private AccessibilitySecurityPolicy mA11ySecurityPolicy;
|
||||
|
||||
@Rule
|
||||
public final TestableContext mContext = new TestableContext(
|
||||
getInstrumentation().getTargetContext(), null);
|
||||
|
||||
// To mock package-private class
|
||||
@Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
|
||||
@Rule
|
||||
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
|
||||
new DexmakerShareClassLoaderRule();
|
||||
|
||||
@Mock private Context mMockContext;
|
||||
@Mock private PackageManager mMockPackageManager;
|
||||
@Mock private UserManager mMockUserManager;
|
||||
@Mock private AppOpsManager mMockAppOpsManager;
|
||||
@Mock private AccessibilityServiceConnection mMockA11yServiceConnection;
|
||||
@Mock private AccessibilityWindowManager mMockA11yWindowManager;
|
||||
@Mock private AppWidgetManagerInternal mMockAppWidgetManager;
|
||||
@Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
|
||||
@Mock
|
||||
private PackageManager mMockPackageManager;
|
||||
@Mock
|
||||
private UserManager mMockUserManager;
|
||||
@Mock
|
||||
private AppOpsManager mMockAppOpsManager;
|
||||
@Mock
|
||||
private AccessibilityServiceConnection mMockA11yServiceConnection;
|
||||
@Mock
|
||||
private AccessibilityWindowManager mMockA11yWindowManager;
|
||||
@Mock
|
||||
private AppWidgetManagerInternal mMockAppWidgetManager;
|
||||
@Mock
|
||||
private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
|
||||
@Mock
|
||||
private AccessibilityServiceInfo mMockA11yServiceInfo;
|
||||
@Mock
|
||||
private PolicyWarningUIController mPolicyWarningUIController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
|
||||
when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
|
||||
when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
|
||||
mContext.setMockPackageManager(mMockPackageManager);
|
||||
mContext.addMockSystemService(Context.USER_SERVICE, mMockUserManager);
|
||||
mContext.addMockSystemService(Context.APP_OPS_SERVICE, mMockAppOpsManager);
|
||||
mContext.getOrCreateTestableResources().addOverride(
|
||||
R.dimen.accessibility_focus_highlight_stroke_width, 1);
|
||||
|
||||
mA11ySecurityPolicy = new AccessibilitySecurityPolicy(mMockContext, mMockA11yUserManager);
|
||||
when(mMockA11yServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
|
||||
when(mMockA11yServiceConnection.getServiceInfo()).thenReturn(mMockA11yServiceInfo);
|
||||
|
||||
mA11ySecurityPolicy = new AccessibilitySecurityPolicy(
|
||||
mPolicyWarningUIController, mContext, mMockA11yUserManager);
|
||||
mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager);
|
||||
mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager);
|
||||
mA11ySecurityPolicy.onSwitchUserLocked(TEST_USER_ID, new HashSet<>());
|
||||
|
||||
when(mMockA11yWindowManager.resolveParentWindowIdLocked(anyInt())).then(returnsFirstArg());
|
||||
}
|
||||
@@ -141,7 +171,7 @@ public class AccessibilitySecurityPolicyTest {
|
||||
final AccessibilityEvent event = AccessibilityEvent.obtain(ALWAYS_DISPATCH_EVENTS[i]);
|
||||
assertTrue("Should dispatch [" + event + "]",
|
||||
mA11ySecurityPolicy.canDispatchAccessibilityEventLocked(
|
||||
UserHandle.USER_SYSTEM,
|
||||
TEST_USER_ID,
|
||||
event));
|
||||
}
|
||||
}
|
||||
@@ -154,28 +184,28 @@ public class AccessibilitySecurityPolicyTest {
|
||||
event.setWindowId(invalidWindowId);
|
||||
assertFalse("Shouldn't dispatch [" + event + "]",
|
||||
mA11ySecurityPolicy.canDispatchAccessibilityEventLocked(
|
||||
UserHandle.USER_SYSTEM,
|
||||
TEST_USER_ID,
|
||||
event));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canDispatchAccessibilityEvent_otherEvents_windowIdIsActive_returnTrue() {
|
||||
when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM))
|
||||
when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID))
|
||||
.thenReturn(WINDOWID);
|
||||
for (int i = 0; i < OTHER_EVENTS.length; i++) {
|
||||
final AccessibilityEvent event = AccessibilityEvent.obtain(OTHER_EVENTS[i]);
|
||||
event.setWindowId(WINDOWID);
|
||||
assertTrue("Should dispatch [" + event + "]",
|
||||
mA11ySecurityPolicy.canDispatchAccessibilityEventLocked(
|
||||
UserHandle.USER_SYSTEM,
|
||||
TEST_USER_ID,
|
||||
event));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canDispatchAccessibilityEvent_otherEvents_windowIdExist_returnTrue() {
|
||||
when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM))
|
||||
when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID))
|
||||
.thenReturn(WINDOWID2);
|
||||
when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID))
|
||||
.thenReturn(AccessibilityWindowInfo.obtain());
|
||||
@@ -184,7 +214,7 @@ public class AccessibilitySecurityPolicyTest {
|
||||
event.setWindowId(WINDOWID);
|
||||
assertTrue("Should dispatch [" + event + "]",
|
||||
mA11ySecurityPolicy.canDispatchAccessibilityEventLocked(
|
||||
UserHandle.USER_SYSTEM,
|
||||
TEST_USER_ID,
|
||||
event));
|
||||
}
|
||||
}
|
||||
@@ -192,24 +222,24 @@ public class AccessibilitySecurityPolicyTest {
|
||||
@Test
|
||||
public void resolveValidReportedPackage_nullPkgName_returnNull() {
|
||||
assertNull(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
|
||||
null, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID));
|
||||
null, Process.SYSTEM_UID, TEST_USER_ID, SYSTEM_PID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveValidReportedPackage_uidIsSystem_returnPkgName() {
|
||||
assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
|
||||
PACKAGE_NAME, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID),
|
||||
PACKAGE_NAME, Process.SYSTEM_UID, TEST_USER_ID, SYSTEM_PID),
|
||||
PACKAGE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveValidReportedPackage_uidAndPkgNameMatched_returnPkgName()
|
||||
throws PackageManager.NameNotFoundException {
|
||||
when(mMockPackageManager.getPackageUidAsUser(PACKAGE_NAME, UserHandle.USER_SYSTEM))
|
||||
when(mMockPackageManager.getPackageUidAsUser(PACKAGE_NAME, TEST_USER_ID))
|
||||
.thenReturn(APP_UID);
|
||||
|
||||
assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
|
||||
PACKAGE_NAME, APP_UID, UserHandle.USER_SYSTEM, APP_PID),
|
||||
PACKAGE_NAME, APP_UID, TEST_USER_ID, APP_PID),
|
||||
PACKAGE_NAME);
|
||||
}
|
||||
|
||||
@@ -225,11 +255,11 @@ public class AccessibilitySecurityPolicyTest {
|
||||
|
||||
when(mMockAppWidgetManager.getHostedWidgetPackages(widgetHostUid))
|
||||
.thenReturn(widgetPackages);
|
||||
when(mMockPackageManager.getPackageUidAsUser(hostPackageName, UserHandle.USER_SYSTEM))
|
||||
when(mMockPackageManager.getPackageUidAsUser(hostPackageName, TEST_USER_ID))
|
||||
.thenReturn(widgetHostUid);
|
||||
|
||||
assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
|
||||
widgetPackageName, widgetHostUid, UserHandle.USER_SYSTEM, widgetHostPid),
|
||||
widgetPackageName, widgetHostUid, TEST_USER_ID, widgetHostPid),
|
||||
widgetPackageName);
|
||||
}
|
||||
|
||||
@@ -240,16 +270,16 @@ public class AccessibilitySecurityPolicyTest {
|
||||
final String[] uidPackages = {PACKAGE_NAME, PACKAGE_NAME2};
|
||||
when(mMockPackageManager.getPackagesForUid(APP_UID))
|
||||
.thenReturn(uidPackages);
|
||||
when(mMockPackageManager.getPackageUidAsUser(invalidPackageName, UserHandle.USER_SYSTEM))
|
||||
when(mMockPackageManager.getPackageUidAsUser(invalidPackageName, TEST_USER_ID))
|
||||
.thenThrow(PackageManager.NameNotFoundException.class);
|
||||
when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
|
||||
.thenReturn(new ArraySet<>());
|
||||
when(mMockContext.checkPermission(
|
||||
eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID)))
|
||||
.thenReturn(PackageManager.PERMISSION_DENIED);
|
||||
mContext.getTestablePermissions().setPermission(
|
||||
Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY,
|
||||
PackageManager.PERMISSION_DENIED);
|
||||
|
||||
assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked(
|
||||
invalidPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID));
|
||||
invalidPackageName, APP_UID, TEST_USER_ID, APP_PID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -260,16 +290,16 @@ public class AccessibilitySecurityPolicyTest {
|
||||
final String[] uidPackages = {PACKAGE_NAME};
|
||||
when(mMockPackageManager.getPackagesForUid(APP_UID))
|
||||
.thenReturn(uidPackages);
|
||||
when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM))
|
||||
when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, TEST_USER_ID))
|
||||
.thenReturn(wantedUid);
|
||||
when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
|
||||
.thenReturn(new ArraySet<>());
|
||||
when(mMockContext.checkPermission(
|
||||
eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID)))
|
||||
.thenReturn(PackageManager.PERMISSION_GRANTED);
|
||||
mContext.getTestablePermissions().setPermission(
|
||||
Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY,
|
||||
PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
assertEquals(wantedPackageName, mA11ySecurityPolicy.resolveValidReportedPackageLocked(
|
||||
wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID));
|
||||
wantedPackageName, APP_UID, TEST_USER_ID, APP_PID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -280,16 +310,16 @@ public class AccessibilitySecurityPolicyTest {
|
||||
final String[] uidPackages = {PACKAGE_NAME};
|
||||
when(mMockPackageManager.getPackagesForUid(APP_UID))
|
||||
.thenReturn(uidPackages);
|
||||
when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM))
|
||||
when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, TEST_USER_ID))
|
||||
.thenReturn(wantedUid);
|
||||
when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
|
||||
.thenReturn(new ArraySet<>());
|
||||
when(mMockContext.checkPermission(
|
||||
eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID)))
|
||||
.thenReturn(PackageManager.PERMISSION_DENIED);
|
||||
mContext.getTestablePermissions().setPermission(
|
||||
Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY,
|
||||
PackageManager.PERMISSION_DENIED);
|
||||
|
||||
assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked(
|
||||
wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID));
|
||||
wantedPackageName, APP_UID, TEST_USER_ID, APP_PID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -301,7 +331,7 @@ public class AccessibilitySecurityPolicyTest {
|
||||
@Test
|
||||
public void computeValidReportedPackages_uidIsAppWidgetHost_returnTargetAndWidgetName() {
|
||||
final int widgetHostUid = APP_UID;
|
||||
final String targetPackageName = PACKAGE_NAME;
|
||||
final String targetPackageName = PACKAGE_NAME;
|
||||
final String widgetPackageName = PACKAGE_NAME2;
|
||||
final ArraySet<String> widgetPackages = new ArraySet<>();
|
||||
widgetPackages.add(widgetPackageName);
|
||||
@@ -320,7 +350,7 @@ public class AccessibilitySecurityPolicyTest {
|
||||
when(mMockA11yServiceConnection.getCapabilities())
|
||||
.thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
|
||||
|
||||
assertFalse(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM,
|
||||
assertFalse(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID,
|
||||
mMockA11yServiceConnection, invalidWindowId));
|
||||
}
|
||||
|
||||
@@ -328,10 +358,10 @@ public class AccessibilitySecurityPolicyTest {
|
||||
public void canGetAccessibilityNodeInfo_hasCapAndWindowIsActive_returnTrue() {
|
||||
when(mMockA11yServiceConnection.getCapabilities())
|
||||
.thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
|
||||
when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM))
|
||||
when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID))
|
||||
.thenReturn(WINDOWID);
|
||||
|
||||
assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM,
|
||||
assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID,
|
||||
mMockA11yServiceConnection, WINDOWID));
|
||||
}
|
||||
|
||||
@@ -339,12 +369,12 @@ public class AccessibilitySecurityPolicyTest {
|
||||
public void canGetAccessibilityNodeInfo_hasCapAndWindowExist_returnTrue() {
|
||||
when(mMockA11yServiceConnection.getCapabilities())
|
||||
.thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
|
||||
when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM))
|
||||
when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID))
|
||||
.thenReturn(WINDOWID2);
|
||||
when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID))
|
||||
.thenReturn(AccessibilityWindowInfo.obtain());
|
||||
|
||||
assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM,
|
||||
assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID,
|
||||
mMockA11yServiceConnection, WINDOWID));
|
||||
}
|
||||
|
||||
@@ -464,8 +494,10 @@ public class AccessibilitySecurityPolicyTest {
|
||||
.thenReturn(currentUserId);
|
||||
doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked(
|
||||
callingUserId);
|
||||
when(mMockContext.checkCallingPermission(any()))
|
||||
.thenReturn(PackageManager.PERMISSION_DENIED);
|
||||
mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS,
|
||||
PackageManager.PERMISSION_DENIED);
|
||||
mContext.getTestablePermissions().setPermission(
|
||||
Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_DENIED);
|
||||
|
||||
spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
|
||||
UserHandle.USER_CURRENT_OR_SELF);
|
||||
@@ -482,8 +514,8 @@ public class AccessibilitySecurityPolicyTest {
|
||||
.thenReturn(currentUserId);
|
||||
doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked(
|
||||
callingUserId);
|
||||
when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS))
|
||||
.thenReturn(PackageManager.PERMISSION_GRANTED);
|
||||
mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS,
|
||||
PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
assertEquals(wantedUserId,
|
||||
spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId));
|
||||
@@ -500,8 +532,8 @@ public class AccessibilitySecurityPolicyTest {
|
||||
.thenReturn(currentUserId);
|
||||
doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked(
|
||||
callingUserId);
|
||||
when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL))
|
||||
.thenReturn(PackageManager.PERMISSION_GRANTED);
|
||||
mContext.getTestablePermissions().setPermission(
|
||||
Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
assertEquals(wantedUserId,
|
||||
spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId));
|
||||
@@ -518,10 +550,10 @@ public class AccessibilitySecurityPolicyTest {
|
||||
.thenReturn(currentUserId);
|
||||
doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked(
|
||||
callingUserId);
|
||||
when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS))
|
||||
.thenReturn(PackageManager.PERMISSION_DENIED);
|
||||
when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL))
|
||||
.thenReturn(PackageManager.PERMISSION_DENIED);
|
||||
mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS,
|
||||
PackageManager.PERMISSION_DENIED);
|
||||
mContext.getTestablePermissions().setPermission(
|
||||
Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_DENIED);
|
||||
|
||||
spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId);
|
||||
}
|
||||
@@ -562,4 +594,57 @@ public class AccessibilitySecurityPolicyTest {
|
||||
APP_UID, PACKAGE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBoundServicesChanged_bindA11yCategoryService_noUIControllerAction() {
|
||||
final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>();
|
||||
boundServices.add(mMockA11yServiceConnection);
|
||||
when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(true);
|
||||
|
||||
mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices);
|
||||
|
||||
verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceBound(anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBoundServicesChanged_unbindA11yCategoryService_noUIControllerAction() {
|
||||
onBoundServicesChanged_bindA11yCategoryService_noUIControllerAction();
|
||||
|
||||
mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>());
|
||||
|
||||
verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceUnbound(anyInt(),
|
||||
any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction() {
|
||||
final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>();
|
||||
boundServices.add(mMockA11yServiceConnection);
|
||||
when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(false);
|
||||
|
||||
mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices);
|
||||
|
||||
verify(mPolicyWarningUIController).onNonA11yCategoryServiceBound(eq(TEST_USER_ID),
|
||||
eq(TEST_COMPONENT_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBoundServicesChanged_unbindNonA11yCategoryService_activateUIControllerAction() {
|
||||
onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction();
|
||||
|
||||
mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>());
|
||||
|
||||
verify(mPolicyWarningUIController).onNonA11yCategoryServiceUnbound(eq(TEST_USER_ID),
|
||||
eq(TEST_COMPONENT_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSwitchUser_differentUser_activateUIControllerAction() {
|
||||
onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction();
|
||||
|
||||
mA11ySecurityPolicy.onSwitchUserLocked(2, new HashSet<>());
|
||||
|
||||
verify(mPolicyWarningUIController).onSwitchUserLocked(eq(2), eq(new HashSet<>()));
|
||||
verify(mPolicyWarningUIController).onNonA11yCategoryServiceUnbound(eq(TEST_USER_ID),
|
||||
eq(TEST_COMPONENT_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 android.app.AlarmManager.RTC_WAKEUP;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_VIEW_AND_CONTROL_ACCESS;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.StatusBarManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.testing.TestableContext;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Tests for the {@link PolicyWarningUIController}.
|
||||
*/
|
||||
public class PolicyWarningUIControllerTest {
|
||||
private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
|
||||
private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
|
||||
"com.android.server.accessibility", "PolicyWarningUIControllerTest");
|
||||
|
||||
private final List<AccessibilityServiceInfo> mEnabledServiceList = new ArrayList<>();
|
||||
|
||||
@Rule
|
||||
public final A11yTestableContext mContext = new A11yTestableContext(
|
||||
getInstrumentation().getTargetContext());
|
||||
@Mock
|
||||
private AlarmManager mAlarmManager;
|
||||
@Mock
|
||||
private NotificationManager mNotificationManager;
|
||||
@Mock
|
||||
private StatusBarManager mStatusBarManager;
|
||||
@Mock
|
||||
private AccessibilityServiceInfo mMockA11yServiceInfo;
|
||||
@Mock
|
||||
private ResolveInfo mMockResolveInfo;
|
||||
@Mock
|
||||
private ServiceInfo mMockServiceInfo;
|
||||
@Mock
|
||||
private Context mSpyContext;
|
||||
@Mock
|
||||
private AccessibilitySecurityPolicy mAccessibilitySecurityPolicy;
|
||||
|
||||
private PolicyWarningUIController mPolicyWarningUIController;
|
||||
private FakeNotificationController mFakeNotificationController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
|
||||
mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
|
||||
mContext.addMockSystemService(StatusBarManager.class, mStatusBarManager);
|
||||
mFakeNotificationController = new FakeNotificationController(mContext);
|
||||
mPolicyWarningUIController = new PolicyWarningUIController(
|
||||
getInstrumentation().getTargetContext().getMainThreadHandler(), mContext,
|
||||
mFakeNotificationController);
|
||||
mPolicyWarningUIController.setAccessibilityPolicyManager(mAccessibilitySecurityPolicy);
|
||||
mPolicyWarningUIController.onSwitchUserLocked(TEST_USER_ID, new HashSet<>());
|
||||
mEnabledServiceList.clear();
|
||||
Settings.Secure.putStringForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
|
||||
"", TEST_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveActionSendNotification_isNonA11yCategoryService_sendNotification() {
|
||||
mEnabledServiceList.add(mMockA11yServiceInfo);
|
||||
mMockResolveInfo.serviceInfo = mMockServiceInfo;
|
||||
when(mMockA11yServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
|
||||
when(mMockA11yServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
|
||||
when(mAccessibilitySecurityPolicy.isA11yCategoryService(
|
||||
mMockA11yServiceInfo)).thenReturn(false);
|
||||
|
||||
mFakeNotificationController.onReceive(mContext, createIntent(TEST_USER_ID,
|
||||
PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
|
||||
TEST_COMPONENT_NAME.flattenToShortString()));
|
||||
|
||||
verify(mNotificationManager).notify(eq(TEST_COMPONENT_NAME.flattenToShortString()),
|
||||
eq(NOTE_A11Y_VIEW_AND_CONTROL_ACCESS), any(
|
||||
Notification.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveActionA11ySettings_launchA11ySettingsAndDismissNotification() {
|
||||
mFakeNotificationController.onReceive(mContext,
|
||||
createIntent(TEST_USER_ID, PolicyWarningUIController.ACTION_A11Y_SETTINGS,
|
||||
TEST_COMPONENT_NAME.flattenToShortString()));
|
||||
|
||||
verifyLaunchA11ySettings();
|
||||
verify(mNotificationManager).cancel(TEST_COMPONENT_NAME.flattenToShortString(),
|
||||
NOTE_A11Y_VIEW_AND_CONTROL_ACCESS);
|
||||
assertNotifiedSettingsEqual(TEST_USER_ID,
|
||||
TEST_COMPONENT_NAME.flattenToShortString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveActionDismissNotification_addToNotifiedSettings() {
|
||||
mFakeNotificationController.onReceive(mContext, createIntent(TEST_USER_ID,
|
||||
PolicyWarningUIController.ACTION_DISMISS_NOTIFICATION,
|
||||
TEST_COMPONENT_NAME.flattenToShortString()));
|
||||
|
||||
assertNotifiedSettingsEqual(TEST_USER_ID,
|
||||
TEST_COMPONENT_NAME.flattenToShortString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onEnabledServicesChangedLocked_serviceDisabled_removedFromNotifiedSettings() {
|
||||
final Set<ComponentName> enabledServices = new HashSet<>();
|
||||
enabledServices.add(TEST_COMPONENT_NAME);
|
||||
mPolicyWarningUIController.onEnabledServicesChangedLocked(TEST_USER_ID, enabledServices);
|
||||
getInstrumentation().waitForIdleSync();
|
||||
receiveActionDismissNotification_addToNotifiedSettings();
|
||||
|
||||
mPolicyWarningUIController.onEnabledServicesChangedLocked(TEST_USER_ID, new HashSet<>());
|
||||
getInstrumentation().waitForIdleSync();
|
||||
|
||||
assertNotifiedSettingsEqual(TEST_USER_ID, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onNonA11yCategoryServiceBound_setAlarm() {
|
||||
mPolicyWarningUIController.onNonA11yCategoryServiceBound(TEST_USER_ID, TEST_COMPONENT_NAME);
|
||||
getInstrumentation().waitForIdleSync();
|
||||
|
||||
verify(mAlarmManager).set(eq(RTC_WAKEUP), anyLong(),
|
||||
eq(PolicyWarningUIController.createPendingIntent(mContext, TEST_USER_ID,
|
||||
PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
|
||||
TEST_COMPONENT_NAME.flattenToShortString())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onNonA11yCategoryServiceUnbound_cancelAlarm() {
|
||||
mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(TEST_USER_ID,
|
||||
TEST_COMPONENT_NAME);
|
||||
getInstrumentation().waitForIdleSync();
|
||||
|
||||
verify(mAlarmManager).cancel(
|
||||
eq(PolicyWarningUIController.createPendingIntent(mContext, TEST_USER_ID,
|
||||
PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
|
||||
TEST_COMPONENT_NAME.flattenToShortString())));
|
||||
}
|
||||
|
||||
private void assertNotifiedSettingsEqual(int userId, String settingString) {
|
||||
final String notifiedServicesSetting = Settings.Secure.getStringForUser(
|
||||
mContext.getContentResolver(),
|
||||
Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
|
||||
userId);
|
||||
assertEquals(settingString, notifiedServicesSetting);
|
||||
}
|
||||
|
||||
private Intent createIntent(int userId, String action, String serviceComponentName) {
|
||||
final Intent intent = new Intent(action);
|
||||
intent.setPackage(mContext.getPackageName())
|
||||
.setIdentifier(serviceComponentName)
|
||||
.putExtra(Intent.EXTRA_USER_ID, userId);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private void verifyLaunchA11ySettings() {
|
||||
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
final ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass(
|
||||
UserHandle.class);
|
||||
verify(mSpyContext).startActivityAsUser(intentCaptor.capture(),
|
||||
any(), userHandleCaptor.capture());
|
||||
assertThat(intentCaptor.getValue().getAction()).isEqualTo(
|
||||
Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
|
||||
assertThat(userHandleCaptor.getValue().getIdentifier()).isEqualTo(TEST_USER_ID);
|
||||
verify(mStatusBarManager).collapsePanels();
|
||||
}
|
||||
|
||||
private class A11yTestableContext extends TestableContext {
|
||||
A11yTestableContext(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
|
||||
mSpyContext.startActivityAsUser(intent, options, user);
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeNotificationController extends
|
||||
PolicyWarningUIController.NotificationController {
|
||||
FakeNotificationController(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AccessibilityServiceInfo> getEnabledServiceInfos() {
|
||||
return mEnabledServiceList;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user