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

Commit b9f0612c authored by Phil Weaver's avatar Phil Weaver
Browse files

Allow a11y shortcut to toggle framework features

Enabling color inversion and color correction initially.

Not showing toast for the framework features, as both are
instantly visible changes and the extra reminder seems like
it will get in the way.

Moving AccessibilityShortcutController to a place where
it can be seen by Settings, so Settings can learn what
framework features to offer the user. Moving tests for that
class to match.

Currently don't have icons for the two framework features.
They will be added in a future CL once I get them.

Also tweaking the warning dialog to include summary
information, if we have it, for the target service.

Bug: 34621067
Bug: 36368472
Test: Adding unit tests for framework features
Change-Id: I32a10989db1c9ad9bf22aae9ad405771b789bc6f
parent d12e276f
Loading
Loading
Loading
Loading
+145 −23
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 Google Inc.
 * Copyright 2017 Google Inc.
 *
 * 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
@@ -14,7 +14,7 @@
 * the License.
 */

package com.android.server.policy;
package com.android.internal.accessibility;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.ActivityManager;
@@ -35,6 +35,7 @@ import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.Window;
import android.view.WindowManager;
@@ -43,20 +44,30 @@ import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import com.android.internal.R;

import java.util.List;
import java.util.Collections;
import java.util.Map;

import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;

import static com.android.internal.util.ArrayUtils.convertToLongArray;

/**
 * Class to help manage the accessibility shortcut
 */
public class AccessibilityShortcutController {
    private static final String TAG = "AccessibilityShortcutController";

    // Dummy component names for framework features
    public static final ComponentName COLOR_INVERSION_COMPONENT_NAME =
            new ComponentName("com.android.server.accessibility", "ColorInversion");
    public static final ComponentName DALTONIZER_COMPONENT_NAME =
            new ComponentName("com.android.server.accessibility", "Daltonizer");

    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
            .build();

    private static Map<ComponentName, ToggleableFrameworkFeatureInfo> sFrameworkShortcutFeaturesMap;

    private final Context mContext;
    private AlertDialog mAlertDialog;
@@ -67,6 +78,15 @@ public class AccessibilityShortcutController {
    // Visible for testing
    public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();

    /**
     * Get the component name string for the service or feature currently assigned to the
     * accessiblity shortcut
     *
     * @param context A valid context
     * @param userId The user ID of interest
     * @return The flattened component name string of the service selected by the user, or the
     *         string for the default service if the user has not made a selection
     */
    public static String getTargetServiceComponentNameString(
            Context context, int userId) {
        final String currentShortcutServiceId = Settings.Secure.getStringForUser(
@@ -78,6 +98,29 @@ public class AccessibilityShortcutController {
        return context.getString(R.string.config_defaultAccessibilityService);
    }

    /**
     * @return An immutable map from dummy component names to feature info for toggling a framework
     *         feature
     */
    public static Map<ComponentName, ToggleableFrameworkFeatureInfo>
        getFrameworkShortcutFeaturesMap() {
        if (sFrameworkShortcutFeaturesMap == null) {
            Map<ComponentName, ToggleableFrameworkFeatureInfo> featuresMap = new ArrayMap<>(2);
            featuresMap.put(COLOR_INVERSION_COMPONENT_NAME,
                    new ToggleableFrameworkFeatureInfo(
                            Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
                            "1" /* Value to enable */, "0" /* Value to disable */,
                            R.string.color_inversion_feature_name));
            featuresMap.put(DALTONIZER_COMPONENT_NAME,
                    new ToggleableFrameworkFeatureInfo(
                            Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
                            "1" /* Value to enable */, "0" /* Value to disable */,
                            R.string.color_correction_feature_name));
            sFrameworkShortcutFeaturesMap = Collections.unmodifiableMap(featuresMap);
        }
        return sFrameworkShortcutFeaturesMap;
    }

    public AccessibilityShortcutController(Context context, Handler handler, int initialUserId) {
        mContext = context;
        mUserId = initialUserId;
@@ -160,8 +203,8 @@ public class AccessibilityShortcutController {
        if ((vibrator != null) && vibrator.hasVibrator()) {
            // Don't check if haptics are disabled, as we need to alert the user that their
            // way of interacting with the phone may change if they activate the shortcut
            long[] vibePattern = PhoneWindowManager.getLongIntArray(mContext.getResources(),
                    R.array.config_longPressVibePattern);
            long[] vibePattern = convertToLongArray(
                    mContext.getResources().getIntArray(R.array.config_longPressVibePattern));
            vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES);
        }

@@ -187,22 +230,24 @@ public class AccessibilityShortcutController {
            }

            // Show a toast alerting the user to what's happening
            final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
            if (serviceInfo == null) {
            final String serviceName = getShortcutFeatureDescription(false /* no summary */);
            if (serviceName == null) {
                Slog.e(TAG, "Accessibility shortcut set to invalid service");
                return;
            }
            // For accessibility services, show a toast explaining what we're doing.
            final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
            if (serviceInfo != null) {
                String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo)
                        ? R.string.accessibility_shortcut_disabling_service
                        : R.string.accessibility_shortcut_enabling_service);
            String toastMessage = String.format(toastMessageFormatString,
                    serviceInfo.getResolveInfo()
                            .loadLabel(mContext.getPackageManager()).toString());
                String toastMessage = String.format(toastMessageFormatString, serviceName);
                Toast warningToast = mFrameworkObjectProvider.makeToastFromText(
                        mContext, toastMessage, Toast.LENGTH_LONG);
                warningToast.getWindowParams().privateFlags |=
                        WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
                warningToast.show();
            }

            mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext)
                    .performAccessibilityShortcut();
@@ -210,18 +255,18 @@ public class AccessibilityShortcutController {
    }

    private AlertDialog createShortcutWarningDialog(int userId) {
        final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
        final String serviceDescription = getShortcutFeatureDescription(true /* Include summary */);

        if (serviceInfo == null) {
        if (serviceDescription == null) {
            return null;
        }

        final String warningMessage = String.format(
                mContext.getString(R.string.accessibility_shortcut_toogle_warning),
                serviceInfo.getResolveInfo().loadLabel(mContext.getPackageManager()).toString());
                serviceDescription);
        final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
                // Use SystemUI context so we pick up any theme set in a vendor overlay
                ActivityThread.currentActivityThread().getSystemUiContext())
                mFrameworkObjectProvider.getSystemUiContext())
                .setTitle(R.string.accessibility_shortcut_warning_dialog_title)
                .setMessage(warningMessage)
                .setCancelable(false)
@@ -253,6 +298,34 @@ public class AccessibilityShortcutController {
                        ComponentName.unflattenFromString(currentShortcutServiceString));
    }

    private String getShortcutFeatureDescription(boolean includeSummary) {
        final String currentShortcutServiceString = getTargetServiceComponentNameString(
                mContext, UserHandle.USER_CURRENT);
        if (currentShortcutServiceString == null) {
            return null;
        }
        final ComponentName targetComponentName =
                ComponentName.unflattenFromString(currentShortcutServiceString);
        final ToggleableFrameworkFeatureInfo frameworkFeatureInfo =
                getFrameworkShortcutFeaturesMap().get(targetComponentName);
        if (frameworkFeatureInfo != null) {
            return frameworkFeatureInfo.getLabel(mContext);
        }
        final AccessibilityServiceInfo serviceInfo = mFrameworkObjectProvider
                .getAccessibilityManagerInstance(mContext).getInstalledServiceInfoWithComponentName(
                        targetComponentName);
        if (serviceInfo == null) {
            return null;
        }
        final PackageManager pm = mContext.getPackageManager();
        String label = serviceInfo.getResolveInfo().loadLabel(pm).toString();
        String summary = serviceInfo.loadSummary(pm).toString();
        if (!includeSummary || TextUtils.isEmpty(summary)) {
            return label;
        }
        return String.format("%s\n%s", label, summary);
    }

    private boolean isServiceEnabled(AccessibilityServiceInfo serviceInfo) {
        AccessibilityManager accessibilityManager =
                mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext);
@@ -264,6 +337,51 @@ public class AccessibilityShortcutController {
        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
    }

    /**
     * Immutable class to hold info about framework features that can be controlled by shortcut
     */
    public static class ToggleableFrameworkFeatureInfo {
        private final String mSettingKey;
        private final String mSettingOnValue;
        private final String mSettingOffValue;
        private final int mLabelStringResourceId;
        // These go to the settings wrapper
        private int mIconDrawableId;

        ToggleableFrameworkFeatureInfo(String settingKey, String settingOnValue,
                String settingOffValue, int labelStringResourceId) {
            mSettingKey = settingKey;
            mSettingOnValue = settingOnValue;
            mSettingOffValue = settingOffValue;
            mLabelStringResourceId = labelStringResourceId;
        }

        /**
         * @return The settings key to toggle between two values
         */
        public String getSettingKey() {
            return mSettingKey;
        }

        /**
         * @return The value to write to settings to turn the feature on
         */
        public String getSettingOnValue() {
            return mSettingOnValue;
        }

        /**
         * @return The value to write to settings to turn the feature off
         */
        public String getSettingOffValue() {
            return mSettingOffValue;
        }

        public String getLabel(Context context) {
            return context.getString(mLabelStringResourceId);
        }
    }

    // Class to allow mocking of static framework calls
    public static class FrameworkObjectProvider {
        public AccessibilityManager getAccessibilityManagerInstance(Context context) {
@@ -277,5 +395,9 @@ public class AccessibilityShortcutController {
        public Toast makeToastFromText(Context context, CharSequence charSequence, int duration) {
            return Toast.makeText(context, charSequence, duration);
        }

        public Context getSystemUiContext() {
            return ActivityThread.currentActivityThread().getSystemUiContext();
        }
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -292,6 +292,15 @@ public class ArrayUtils {
        return array;
    }

    public static @Nullable long[] convertToLongArray(@Nullable int[] intArray) {
        if (intArray == null) return null;
        long[] array = new long[intArray.length];
        for (int i = 0; i < intArray.length; i++) {
            array[i] = (long) intArray[i];
        }
        return array;
    }

    /**
     * Adds value to given array if not already present, providing set-like
     * behavior.
+8 −0
Original line number Diff line number Diff line
@@ -4031,6 +4031,14 @@
    shortcut enabled.-->
    <string name="leave_accessibility_shortcut_on">Use Shortcut</string>

    <!-- Title of Color Inversion feature shown in the warning dialog about the accessibility
    shortcut. -->
    <string name="color_inversion_feature_name">Color Inversion</string>

    <!-- Title of Color Correction feature, which is mostly used to help users who are colorblind,
     shown in the warning dialog about the accessibility shortcut. -->
    <string name="color_correction_feature_name">Color Correction</string>

    <!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility
    service.-->
    <string name="accessibility_shortcut_enabling_service">Accessibility Shortcut turned
+2 −0
Original line number Diff line number Diff line
@@ -2908,6 +2908,8 @@
  <java-symbol type="string" name="accessibility_shortcut_disabling_service" />
  <java-symbol type="string" name="disable_accessibility_shortcut" />
  <java-symbol type="string" name="leave_accessibility_shortcut_on" />
  <java-symbol type="string" name="color_inversion_feature_name" />
  <java-symbol type="string" name="color_correction_feature_name" />
  <java-symbol type="string" name="config_defaultAccessibilityService" />

  <!-- Accessibility Button -->
+1 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ LOCAL_DX_FLAGS := --core-library
LOCAL_JACK_FLAGS := --multi-dex native
LOCAL_AAPT_FLAGS = -0 dat -0 gld -c fa
LOCAL_STATIC_JAVA_LIBRARIES := \
    frameworks-base-testutils \
    core-tests-support \
    android-common \
    frameworks-core-util-lib \
Loading