summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author PETER LIANG <peterliang@google.com> 2020-01-10 13:33:24 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-01-10 13:33:24 +0000
commitb9816f6e5d1ba30fc4a4fc3f19e4f390525bc2c7 (patch)
treea2a6b3906bb9ca4ba3283b08d04105ced1be1a3a
parentb5f37e0251748b5129016aecb82ef7c3601a1b50 (diff)
parent8089bc4daa3243d6588ecb6e72daed9ef4e14e98 (diff)
Merge "Multiple shortcut menu for android R (4/n)."
-rw-r--r--core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java260
1 files changed, 254 insertions, 6 deletions
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index 0a0d05ce8fc8..de204badfd0d 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -16,6 +16,7 @@
package com.android.internal.app;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -24,13 +25,17 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -49,7 +54,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
/**
* Activity used to display and persist a service or feature target for the Accessibility button.
@@ -59,12 +67,46 @@ public class AccessibilityButtonChooserActivity extends Activity {
private static final String MAGNIFICATION_COMPONENT_ID =
"com.android.server.accessibility.MagnificationController";
+ private static final char SERVICES_SEPARATOR = ':';
+ private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+ private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType(
+ ACCESSIBILITY_BUTTON);
+ private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType(
+ ACCESSIBILITY_SHORTCUT_KEY);
+
private int mShortcutType;
private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
private AlertDialog mAlertDialog;
private TargetAdapter mTargetAdapter;
/**
+ * Annotation for different user shortcut type UI type.
+ *
+ * {@code DEFAULT} for displaying default value.
+ * {@code SOFTWARE} for displaying specifying the accessibility services or features which
+ * choose accessibility button in the navigation bar as preferred shortcut.
+ * {@code HARDWARE} for displaying specifying the accessibility services or features which
+ * choose accessibility shortcut as preferred shortcut.
+ * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
+ * tapping screen 3 times as preferred shortcut.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ UserShortcutType.DEFAULT,
+ UserShortcutType.SOFTWARE,
+ UserShortcutType.HARDWARE,
+ UserShortcutType.TRIPLETAP,
+ })
+ /** Denotes the user shortcut type. */
+ public @interface UserShortcutType {
+ int DEFAULT = 0;
+ int SOFTWARE = 1; // 1 << 0
+ int HARDWARE = 2; // 1 << 1
+ int TRIPLETAP = 4; // 1 << 2
+ }
+
+ /**
* Annotation for different accessibilityService fragment UI type.
*
* {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service
@@ -395,19 +437,63 @@ public class AccessibilityButtonChooserActivity extends Activity {
}
private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
- // TODO(b/146967898): disable service when deleting the target and the target only have
- // last one shortcut item, only remove it from shortcut list otherwise.
- if ((mShortcutType == ACCESSIBILITY_BUTTON) && (mTargets.get(position).mFragmentType
- != AccessibilityServiceFragmentType.LEGACY)) {
- mTargets.remove(position);
- mTargetAdapter.notifyDataSetChanged();
+ final AccessibilityButtonTarget target = mTargets.get(position);
+ final ComponentName targetComponentName =
+ ComponentName.unflattenFromString(target.getId());
+
+ switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.INVISIBLE:
+ onInvisibleTargetDeleted(targetComponentName);
+ break;
+ case AccessibilityServiceFragmentType.INTUITIVE:
+ onIntuitiveTargetDeleted(targetComponentName);
+ break;
+ case AccessibilityServiceFragmentType.LEGACY:
+ case AccessibilityServiceFragmentType.BOUNCE:
+ // Do nothing
+ break;
+ default:
+ throw new IllegalStateException("Unexpected fragment type");
}
+ mTargets.remove(position);
+ mTargetAdapter.notifyDataSetChanged();
+
if (mTargets.isEmpty()) {
mAlertDialog.dismiss();
}
}
+ private void onInvisibleTargetDeleted(ComponentName componentName) {
+ if (mShortcutType == ACCESSIBILITY_BUTTON) {
+ optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+
+ if (!hasValueInSettings(this,
+ ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) {
+ setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ }
+ } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+
+ if (!hasValueInSettings(this,
+ ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
+ setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+ }
+ }
+
+ private void onIntuitiveTargetDeleted(ComponentName componentName) {
+ if (mShortcutType == ACCESSIBILITY_BUTTON) {
+ optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+ } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+ } else {
+ throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+ }
+ }
+
private void onCancelButtonClicked() {
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
mTargetAdapter.notifyDataSetChanged();
@@ -437,4 +523,166 @@ public class AccessibilityButtonChooserActivity extends Activity {
mAlertDialog.getListView().setOnItemClickListener(
isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected);
}
+
+ /**
+ * @return the set of enabled accessibility services for {@param userId}. If there are no
+ * services, it returns the unmodifiable {@link Collections#emptySet()}.
+ */
+ private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) {
+ final String enabledServicesSetting = Settings.Secure.getStringForUser(
+ context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
+ if (TextUtils.isEmpty(enabledServicesSetting)) {
+ return Collections.emptySet();
+ }
+
+ final Set<ComponentName> enabledServices = new HashSet<>();
+ final TextUtils.StringSplitter colonSplitter =
+ new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+ colonSplitter.setString(enabledServicesSetting);
+
+ for (String componentNameString : colonSplitter) {
+ final ComponentName enabledService = ComponentName.unflattenFromString(
+ componentNameString);
+ if (enabledService != null) {
+ enabledServices.add(enabledService);
+ }
+ }
+
+ return enabledServices;
+ }
+
+ /**
+ * Changes an accessibility component's state.
+ */
+ private void setAccessibilityServiceState(Context context, ComponentName componentName,
+ boolean enabled) {
+ setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId());
+ }
+
+ /**
+ * Changes an accessibility component's state for {@param userId}.
+ */
+ private void setAccessibilityServiceState(Context context, ComponentName componentName,
+ boolean enabled, int userId) {
+ Set<ComponentName> enabledServices = getEnabledServicesFromSettings(
+ context, userId);
+
+ if (enabledServices.isEmpty()) {
+ enabledServices = new ArraySet<>(/* capacity= */ 1);
+ }
+
+ if (enabled) {
+ enabledServices.add(componentName);
+ } else {
+ enabledServices.remove(componentName);
+ }
+
+ final StringBuilder enabledServicesBuilder = new StringBuilder();
+ for (ComponentName enabledService : enabledServices) {
+ enabledServicesBuilder.append(enabledService.flattenToString());
+ enabledServicesBuilder.append(
+ SERVICES_SEPARATOR);
+ }
+
+ final int enabledServicesBuilderLength = enabledServicesBuilder.length();
+ if (enabledServicesBuilderLength > 0) {
+ enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
+ }
+
+ Settings.Secure.putStringForUser(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ enabledServicesBuilder.toString(), userId);
+ }
+
+ /**
+ * Opts out component name into colon-separated {@code shortcutType} key's string in Settings.
+ *
+ * @param context The current context.
+ * @param shortcutType The preferred shortcut type user selected.
+ * @param componentName The component name that need to be opted out from Settings.
+ */
+ private void optOutValueFromSettings(
+ Context context, int shortcutType, ComponentName componentName) {
+ final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+ final String targetsKey = convertToKey(shortcutType);
+ final String targetsValue = Settings.Secure.getString(context.getContentResolver(),
+ targetsKey);
+
+ if (TextUtils.isEmpty(targetsValue)) {
+ return;
+ }
+
+ sStringColonSplitter.setString(targetsValue);
+ while (sStringColonSplitter.hasNext()) {
+ final String name = sStringColonSplitter.next();
+ if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) {
+ continue;
+ }
+ joiner.add(name);
+ }
+
+ Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString());
+ }
+
+ /**
+ * Returns if component name existed in Settings.
+ *
+ * @param context The current context.
+ * @param shortcutType The preferred shortcut type user selected.
+ * @param componentName The component name that need to be checked existed in Settings.
+ * @return {@code true} if componentName existed in Settings.
+ */
+ private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType,
+ @NonNull ComponentName componentName) {
+ final String targetKey = convertToKey(shortcutType);
+ final String targetString = Settings.Secure.getString(context.getContentResolver(),
+ targetKey);
+
+ if (TextUtils.isEmpty(targetString)) {
+ return false;
+ }
+
+ sStringColonSplitter.setString(targetString);
+ while (sStringColonSplitter.hasNext()) {
+ final String name = sStringColonSplitter.next();
+ if ((componentName.flattenToString()).equals(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Converts {@link UserShortcutType} to key in Settings.
+ *
+ * @param type The shortcut type.
+ * @return Mapping key in Settings.
+ */
+ private String convertToKey(@UserShortcutType int type) {
+ switch (type) {
+ case UserShortcutType.SOFTWARE:
+ return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
+ case UserShortcutType.HARDWARE:
+ return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+ case UserShortcutType.TRIPLETAP:
+ return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported user shortcut type: " + type);
+ }
+ }
+
+ private static @UserShortcutType int convertToUserType(@ShortcutType int type) {
+ switch (type) {
+ case ACCESSIBILITY_BUTTON:
+ return UserShortcutType.SOFTWARE;
+ case ACCESSIBILITY_SHORTCUT_KEY:
+ return UserShortcutType.HARDWARE;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported shortcut type:" + type);
+ }
+ }
}