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

Commit d17139aa authored by Jason Hsu's avatar Jason Hsu Committed by Android (Google) Code Review
Browse files

Merge changes from topic "basic a11yFM controller"

* changes:
  Handle the phone get booted case to show accessibility floating menu
  Handle the basic logic to show or hide accessibility floating menu
  Observer for the accessibility button mode and notify its listeners for the changes
parents 1d873cb4 bf0ab716
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);
+101 −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 static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;

import android.annotation.IntDef;
import android.annotation.MainThread;
import android.content.Context;
import android.provider.Settings;
import android.util.Log;

import com.android.systemui.dagger.SysUISingleton;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Inject;

/**
 * Observes changes of the accessibility button mode
 * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE} and notify its listeners.
 */
@MainThread
@SysUISingleton
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;

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

    /** Listener for accessibility button mode changes. */
    public interface ModeChangedListener {

        /**
         * Called when accessibility button mode changes.
         *
         * @param mode Current accessibility button mode
         */
        void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode);
    }

    @Inject
    public AccessibilityButtonModeObserver(Context context) {
        super(context, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
    }

    @Override
    void onValueChanged(ModeChangedListener listener, String value) {
        final int mode = parseAccessibilityButtonMode(value);
        listener.onAccessibilityButtonModeChanged(mode);
    }

    /**
     * Gets the current accessibility button mode from the current user's settings.
     *
     * See {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE}.
     */
    public int getCurrentAccessibilityButtonMode() {
        final String value = getSettingsValue();

        return parseAccessibilityButtonMode(value);
    }

    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);
}
+145 −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 android.view.accessibility.AccessibilityManager;

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,
        AccessibilityManager.AccessibilityStateChangeListener {

    private final Context mContext;
    private final AccessibilityManager mAccessibilityManager;
    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;
        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);

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

        // Accessibility floating menu widget needs accessibility service to work, but system
        // accessibility might be unavailable during the phone get booted, hence it needs to wait
        // for accessibility manager callback to work.
        mAccessibilityManager.addAccessibilityStateChangeListener(this);
        if (mAccessibilityManager.isEnabled()) {
            handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
            mAccessibilityManager.removeAccessibilityStateChangeListener(this);
        }
    }

    /**
     * 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);
    }

    /**
     * Handles visibility of the accessibility floating menu when system accessibility state
     * changes.
     * If system accessibility become available onAccessibilityStateChanged(true), then we don't
     * need to listen to this listener anymore.
     *
     * @param enabled Whether accessibility is enabled.
     */
    @Override
    public void onAccessibilityStateChanged(boolean enabled) {
        if (enabled) {
            handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
        }

        mAccessibilityManager.removeAccessibilityStateChangeListener(this);
    }

    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