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

Commit 5154c4cf authored by jasonwshsu's avatar jasonwshsu
Browse files

Handle the basic logic to show or hide accessibility floating menu

* AccessibilityFloatingMenuController will listen to the changes from AccessibilityButtonTargetsObserver and AccessibilityButtonModeObserver then handle the visibility of FloatingMenu
* Add abstract class SecureSettingsContentObserver to handle a registry of listeners of specific types and dispatch changes to child

Cherry picked from commit 523514fd

Bug: 173940864
Test: atest AccessibilityButtonTargetsObserverTest AccessibilityButtonModeObserverTest AccessibilityFloatingMenuControllerTest
Change-Id: I07fded55340644bce84a3f17a7a4da950268d2d2
Merged-In: I07fded55340644bce84a3f17a7a4da950268d2d2
parent c1e28cc2
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -34,6 +34,9 @@ import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.clock.ClockManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -284,6 +287,8 @@ public class Dependency {
    @Inject Lazy<IWindowManager> mIWindowManager;
    @Inject Lazy<OverviewProxyService> mOverviewProxyService;
    @Inject Lazy<NavigationModeController> mNavBarModeController;
    @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver;
    @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController;
    @Inject Lazy<EnhancedEstimates> mEnhancedEstimates;
    @Inject Lazy<VibratorHelper> mVibratorHelper;
    @Inject Lazy<IStatusBarService> mIStatusBarService;
@@ -294,6 +299,7 @@ public class Dependency {
    @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
    @Inject Lazy<AppOpsController> mAppOpsController;
    @Inject Lazy<NavigationBarController> mNavigationBarController;
    @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
    @Inject Lazy<StatusBarStateController> mStatusBarStateController;
    @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
    @Inject Lazy<NotificationGroupAlertTransferHelper> mNotificationGroupAlertTransferHelper;
@@ -470,6 +476,11 @@ public class Dependency {

        mProviders.put(NavigationModeController.class, mNavBarModeController::get);

        mProviders.put(AccessibilityButtonModeObserver.class,
                mAccessibilityButtonModeObserver::get);
        mProviders.put(AccessibilityButtonTargetsObserver.class,
                mAccessibilityButtonListController::get);

        mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get);

        mProviders.put(VibratorHelper.class, mVibratorHelper::get);
@@ -489,6 +500,9 @@ public class Dependency {

        mProviders.put(NavigationBarController.class, mNavigationBarController::get);

        mProviders.put(AccessibilityFloatingMenuController.class,
                mAccessibilityFloatingMenuController::get);

        mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
        mProviders.put(NotificationLockscreenUserManager.class,
                mNotificationLockscreenUserManager::get);
+25 −66
Original line number Diff line number Diff line
@@ -21,23 +21,14 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGAT

import android.annotation.IntDef;
import android.annotation.MainThread;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;

import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import javax.inject.Inject;

@@ -47,23 +38,20 @@ import javax.inject.Inject;
 */
@MainThread
@SysUISingleton
public class AccessibilityButtonModeObserver {
public class AccessibilityButtonModeObserver extends
        SecureSettingsContentObserver<AccessibilityButtonModeObserver.ModeChangedListener> {

    private static final String TAG = "A11yButtonModeObserver";

    private static final int ACCESSIBILITY_BUTTON_MODE_DEFAULT =
            ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;

    private final ContentResolver mContentResolver;
    @VisibleForTesting
    final ContentObserver mContentObserver;

    private final List<ModeChangedListener> mListeners = new ArrayList<>();

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR,
            ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU
    })
    private @interface AccessibilityButtonMode {}
    public @interface AccessibilityButtonMode {}

    /** Listener for accessibility button mode changes. */
    public interface ModeChangedListener {
@@ -71,54 +59,20 @@ public class AccessibilityButtonModeObserver {
        /**
         * Called when accessibility button mode changes.
         *
         * @param mode Current accessibility button mode.
         * @param mode Current accessibility button mode
         */
        void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode);
    }

    @Inject
    public AccessibilityButtonModeObserver(Context context) {
        mContentResolver = context.getContentResolver();
        mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
            @Override
            public void onChange(boolean selfChange) {
                updateAccessibilityButtonModeChanged();
            }
        };
    }

    /**
     * Registers a listener to receive updates from settings key {@link
     * Settings.Secure#ACCESSIBILITY_BUTTON_MODE}.
     *
     * @param listener A {@link ModeChangedListener} object.
     */
    public void addListener(@NonNull ModeChangedListener listener) {
        Objects.requireNonNull(listener, "listener must be non-null");

        mListeners.add(listener);

        if (mListeners.size() == 1) {
            mContentResolver.registerContentObserver(
                    Settings.Secure.getUriFor(
                            Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */
                    false, mContentObserver);
        }
        super(context, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
    }

    /**
     * Unregisters a listener previously registered with {@link #addListener(ModeChangedListener)}.
     *
     * @param listener A {@link ModeChangedListener} object.
     */
    public void removeListener(@NonNull ModeChangedListener listener) {
        Objects.requireNonNull(listener, "listener must be non-null");

        mListeners.remove(listener);

        if (mListeners.isEmpty()) {
            mContentResolver.unregisterContentObserver(mContentObserver);
        }
    @Override
    void onValueChanged(ModeChangedListener listener, String value) {
        final int mode = parseAccessibilityButtonMode(value);
        listener.onAccessibilityButtonModeChanged(mode);
    }

    /**
@@ -126,17 +80,22 @@ public class AccessibilityButtonModeObserver {
     *
     * See {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE}.
     */
    @AccessibilityButtonMode
    public int getCurrentAccessibilityButtonMode() {
        return Settings.Secure.getInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
                ACCESSIBILITY_BUTTON_MODE_DEFAULT);
        final String value = getSettingsValue();

        return parseAccessibilityButtonMode(value);
    }

    private void updateAccessibilityButtonModeChanged() {
        final int mode = getCurrentAccessibilityButtonMode();
        final int listenerSize = mListeners.size();
        for (int i = 0; i < listenerSize; i++) {
            mListeners.get(i).onAccessibilityButtonModeChanged(mode);
    private int parseAccessibilityButtonMode(String value) {
        int mode;

        try {
            mode = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            Log.e(TAG, "Invalid string for  " + e);
            mode = ACCESSIBILITY_BUTTON_MODE_DEFAULT;
        }

        return mode;
    }
}
+66 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.accessibility;

import android.content.Context;
import android.provider.Settings;

import androidx.annotation.MainThread;
import androidx.annotation.Nullable;

import com.android.systemui.dagger.SysUISingleton;

import javax.inject.Inject;

/**
 * Controller for tracking the current accessibility button list.
 *
 * @see Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS
 */
@MainThread
@SysUISingleton
public class AccessibilityButtonTargetsObserver extends
        SecureSettingsContentObserver<AccessibilityButtonTargetsObserver.TargetsChangedListener> {

    /** Listener for accessibility button targets changes. */
    public interface TargetsChangedListener {

        /**
         * Called when accessibility button targets changes.
         *
         * @param targets Current content of {@link Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}
         */
        void onAccessibilityButtonTargetsChanged(String targets);
    }

    @Inject
    public AccessibilityButtonTargetsObserver(Context context) {
        super(context, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
    }

    @Override
    void onValueChanged(TargetsChangedListener listener, String value) {
        listener.onAccessibilityButtonTargetsChanged(value);
    }

    /** Returns the current string from settings key
     *  {@link Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}. */
    @Nullable
    public String getCurrentAccessibilityButtonTargets() {
        return getSettingsValue();
    }
}
+121 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.accessibility;

import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;

import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Provides basic methods for adding, removing arbitrary listeners and inquiry given {@code
 * secureSettingsKey} value; it must comes from {@link Settings.Secure}.
 *
 * This abstract class is intended to be subclassed and specialized to maintain
 * a registry of listeners of specific types and dispatch changes to them.
 *
 * @param <T> The listener type
 */
public abstract class SecureSettingsContentObserver<T> {

    private final ContentResolver mContentResolver;
    @VisibleForTesting
    final ContentObserver mContentObserver;

    private final String mKey;

    @VisibleForTesting
    final List<T> mListeners = new ArrayList<>();

    protected SecureSettingsContentObserver(Context context, String secureSettingsKey) {
        mKey = secureSettingsKey;
        mContentResolver = context.getContentResolver();
        mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
            @Override
            public void onChange(boolean selfChange) {
                updateValueChanged();
            }
        };
    }

    /**
     * Registers a listener to receive updates from given settings key {@code secureSettingsKey}.
     *
     * @param listener A listener to be added to receive the changes
     */
    public void addListener(@NonNull T listener) {
        Objects.requireNonNull(listener, "listener must be non-null");

        mListeners.add(listener);

        if (mListeners.size() == 1) {
            mContentResolver.registerContentObserver(
                    Settings.Secure.getUriFor(mKey), /* notifyForDescendants= */
                    false, mContentObserver);
        }
    }

    /**
     * Unregisters a listener previously registered with {@link #addListener(T listener)}.
     *
     * @param listener A listener to be removed from receiving the changes
     */
    public void removeListener(@NonNull T listener) {
        Objects.requireNonNull(listener, "listener must be non-null");

        mListeners.remove(listener);

        if (mListeners.isEmpty()) {
            mContentResolver.unregisterContentObserver(mContentObserver);
        }
    }

    /**
     * Gets the value from the current user's secure settings.
     *
     * See {@link Settings.Secure}.
     */
    public final String getSettingsValue() {
        return Settings.Secure.getString(mContentResolver, mKey);
    }

    private void updateValueChanged() {
        final String value = getSettingsValue();
        final int listenerSize = mListeners.size();
        for (int i = 0; i < listenerSize; i++) {
            onValueChanged(mListeners.get(i), value);
        }
    }

    /**
     * Called when the registered value from {@code secureSettingsKey} changes.
     *
     * @param listener A listener could be used to receive the updates
     * @param value Content changed value from {@code secureSettingsKey}
     */
    abstract void onValueChanged(T listener, String value);
}
+117 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.accessibility.floatingmenu;

import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;

import android.content.Context;
import android.text.TextUtils;

import androidx.annotation.MainThread;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.dagger.SysUISingleton;

import javax.inject.Inject;

/** A controller to handle the lifecycle of accessibility floating menu. */
@MainThread
@SysUISingleton
public class AccessibilityFloatingMenuController implements
        AccessibilityButtonModeObserver.ModeChangedListener,
        AccessibilityButtonTargetsObserver.TargetsChangedListener {

    private final Context mContext;
    private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
    private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;

    @VisibleForTesting
    IAccessibilityFloatingMenu mFloatingMenu;
    private int mBtnMode;
    private String mBtnTargets;

    @Inject
    public AccessibilityFloatingMenuController(Context context,
            AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
            AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
        mContext = context;
        mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
        mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;

        mAccessibilityButtonModeObserver.addListener(this);
        mAccessibilityButtonTargetsObserver.addListener(this);
        mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
        mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();

        handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
    }

    /**
     * Handles visibility of the accessibility floating menu when accessibility button mode changes.
     *
     * @param mode Current accessibility button mode.
     */
    @Override
    public void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode) {
        mBtnMode = mode;
        handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
    }

    /**
     * Handles visibility of the accessibility floating menu when accessibility button targets
     * changes.
     * List should come from {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
     * @param targets Current accessibility button list.
     */
    @Override
    public void onAccessibilityButtonTargetsChanged(String targets) {
        mBtnTargets = targets;
        handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
    }

    private void handleFloatingMenuVisibility(@AccessibilityButtonMode int mode, String targets) {
        if (shouldShowFloatingMenu(mode, targets)) {
            showFloatingMenu();
        } else {
            destroyFloatingMenu();
        }
    }

    private boolean shouldShowFloatingMenu(@AccessibilityButtonMode int mode, String targets) {
        return mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU && !TextUtils.isEmpty(targets);
    }

    private void showFloatingMenu() {
        if (mFloatingMenu == null) {
            mFloatingMenu = new AccessibilityFloatingMenu(mContext);
        }

        mFloatingMenu.show();
    }

    private void destroyFloatingMenu() {
        if (mFloatingMenu == null) {
            return;
        }

        mFloatingMenu.hide();
        mFloatingMenu = null;
    }
}
Loading