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

Commit 7efe0b9b authored by ryanlwlin's avatar ryanlwlin
Browse files

Disable volume-key-accessibility shortcut upon exiting setup wizard

The volume-key-accessibility shortcut is often triggered accidentally,
which causes much frustration for users who are not familiar with it,
and do not know how to revert.

To help prevent this from happening, we will set the shortcut targets
to empty if setup has been completed without the enabled
AccessibilityService associated with the default shortcut.

Bug: 226866175
Test: manual test:
     1. activate shortcut during setup wizard then finish setup wizard,
	observe settings value of shortcut targets. It should not be
	empty.
     2. Finish setup wizard then observe esttings value of shortcut
	targets. It should be empty.
     3. Create a new user and run step 2 and 3 separately.
Change-Id: Iaf99b850043f7606d6b183f0b4822c36287fa106
parent 0bdead62
Loading
Loading
Loading
Loading
+106 −10
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.internal.accessibility;

import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;

@@ -107,6 +108,8 @@ public class AccessibilityShortcutController {

    private final Context mContext;
    private final Handler mHandler;
    private final UserSetupCompleteObserver  mUserSetupCompleteObserver;

    private AlertDialog mAlertDialog;
    private boolean mIsShortcutEnabled;
    private boolean mEnabledOnLockScreen;
@@ -114,11 +117,11 @@ public class AccessibilityShortcutController {

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            DialogStaus.NOT_SHOWN,
            DialogStaus.SHOWN,
            DialogStatus.NOT_SHOWN,
            DialogStatus.SHOWN,
    })
    /** Denotes the user shortcut type. */
    private @interface DialogStaus {
    private @interface DialogStatus {
        int NOT_SHOWN = 0;
        int SHOWN  = 1;
    }
@@ -163,6 +166,7 @@ public class AccessibilityShortcutController {
        mContext = context;
        mHandler = handler;
        mUserId = initialUserId;
        mUserSetupCompleteObserver = new UserSetupCompleteObserver(handler, initialUserId);

        // Keep track of state of shortcut settings
        final ContentObserver co = new ContentObserver(handler) {
@@ -188,6 +192,7 @@ public class AccessibilityShortcutController {
    public void setCurrentUser(int currentUserId) {
        mUserId = currentUserId;
        onSettingsChanged();
        mUserSetupCompleteObserver.onUserSwitched(currentUserId);
    }

    /**
@@ -206,7 +211,7 @@ public class AccessibilityShortcutController {
        final ContentResolver cr = mContext.getContentResolver();
        // Enable the shortcut from the lockscreen by default if the dialog has been shown
        final int dialogAlreadyShown = Settings.Secure.getIntForUser(
                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.NOT_SHOWN,
                mUserId);
        mEnabledOnLockScreen = Settings.Secure.getIntForUser(
                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
@@ -222,7 +227,7 @@ public class AccessibilityShortcutController {
        final ContentResolver cr = mContext.getContentResolver();
        final int userId = ActivityManager.getCurrentUser();
        final int dialogAlreadyShown = Settings.Secure.getIntForUser(
                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.NOT_SHOWN,
                userId);
        // Play a notification vibration
        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
@@ -234,7 +239,7 @@ public class AccessibilityShortcutController {
            vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES);
        }

        if (dialogAlreadyShown == 0) {
        if (dialogAlreadyShown == DialogStatus.NOT_SHOWN) {
            // The first time, we show a warning rather than toggle the service to give the user a
            // chance to turn off this feature before stuff gets enabled.
            mAlertDialog = createShortcutWarningDialog(userId);
@@ -250,7 +255,7 @@ public class AccessibilityShortcutController {
            w.setAttributes(attr);
            mAlertDialog.show();
            Settings.Secure.putIntForUser(
                    cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.SHOWN,
                    cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.SHOWN,
                    userId);
        } else {
            playNotificationTone();
@@ -320,13 +325,13 @@ public class AccessibilityShortcutController {
                            // If canceled, treat as if the dialog has never been shown
                            Settings.Secure.putIntForUser(mContext.getContentResolver(),
                                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
                                    DialogStaus.NOT_SHOWN, userId);
                                    DialogStatus.NOT_SHOWN, userId);
                        })
                .setOnCancelListener((DialogInterface d) -> {
                    // If canceled, treat as if the dialog has never been shown
                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
                            Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
                            DialogStaus.NOT_SHOWN, userId);
                            DialogStatus.NOT_SHOWN, userId);
                })
                .create();
        return alertDialog;
@@ -398,7 +403,7 @@ public class AccessibilityShortcutController {
        AccessibilityManager accessibilityManager =
                mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext);
        return accessibilityManager.getEnabledAccessibilityServiceList(
                AccessibilityServiceInfo.FEEDBACK_ALL_MASK).contains(serviceInfo);
                FEEDBACK_ALL_MASK).contains(serviceInfo);
    }

    private boolean hasFeatureLeanback() {
@@ -559,6 +564,97 @@ public class AccessibilityShortcutController {
        }
    }

    private class UserSetupCompleteObserver extends ContentObserver {

        private boolean mIsRegistered = false;
        private int mUserId;

        /**
         * Creates a content observer.
         *
         * @param handler The handler to run {@link #onChange} on, or null if none.
         * @param userId The current user id.
         */
        UserSetupCompleteObserver(Handler handler, int userId) {
            super(handler);
            mUserId = userId;
            if (!isUserSetupComplete()) {
                registerObserver();
            }
        }

        private boolean isUserSetupComplete() {
            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
                    Settings.Secure.USER_SETUP_COMPLETE, 0, mUserId) == 1;
        }

        private void registerObserver() {
            if (mIsRegistered) {
                return;
            }
            mContext.getContentResolver().registerContentObserver(
                    Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE),
                    false, this, mUserId);
            mIsRegistered = true;
        }

        @Override
        public void onChange(boolean selfChange) {
            if (isUserSetupComplete()) {
                unregisterObserver();
                setEmptyShortcutTargetIfNeeded();
            }
        }

        private void unregisterObserver() {
            if (!mIsRegistered) {
                return;
            }
            mContext.getContentResolver().unregisterContentObserver(this);
            mIsRegistered = false;
        }

        /**
         * Sets empty shortcut target if shortcut targets is not assigned and there is no any
         * enabled service matching the default target after the setup wizard completed.
         *
         */
        private void setEmptyShortcutTargetIfNeeded() {
            final ContentResolver contentResolver = mContext.getContentResolver();

            final String shortcutTargets = Settings.Secure.getStringForUser(contentResolver,
                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, mUserId);
            if (shortcutTargets != null) {
                return;
            }

            final String defaultShortcutTarget = mContext.getString(
                    R.string.config_defaultAccessibilityService);
            final List<AccessibilityServiceInfo> enabledServices =
                    mFrameworkObjectProvider.getAccessibilityManagerInstance(
                            mContext).getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
            for (int i = enabledServices.size() - 1; i >= 0; i--) {
                if (TextUtils.equals(defaultShortcutTarget, enabledServices.get(i).getId())) {
                    return;
                }
            }

            Settings.Secure.putStringForUser(contentResolver,
                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", mUserId);
        }

        void onUserSwitched(int userId) {
            if (mUserId == userId) {
                return;
            }
            unregisterObserver();
            mUserId = userId;
            if (!isUserSetupComplete()) {
                registerObserver();
            }
        }
    }

    /**
     * Immutable class to hold info about framework features that can be controlled by shortcut
     */