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

Commit a389ff59 authored by Menghan Li's avatar Menghan Li
Browse files

refactor(A11yFeedback): Simply feedback logic into menu controller

Bug: 393980229
Test: atest FeedbackMenuControllerTest
Flag: com.android.server.accessibility.enable_low_vision_generic_feedback
Change-Id: I03af00957c2bcca1d1cc81970eccad6dd69bb4ac
parent 9066ecf6
Loading
Loading
Loading
Loading
+2 −37
Original line number Diff line number Diff line
@@ -30,9 +30,6 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.accessibility.AccessibilityManager;

import androidx.annotation.NonNull;
@@ -45,6 +42,7 @@ import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.content.PackageMonitor;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType;
import com.android.settings.accessibility.actionbar.FeedbackMenuController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -105,8 +103,6 @@ public class AccessibilitySettings extends DashboardFragment implements
    // presentation.
    private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;

    static final int MENU_ID_SEND_FEEDBACK = 0;

    private final Handler mHandler = new Handler();

    private final Runnable mUpdateRunnable = new Runnable() {
@@ -151,8 +147,6 @@ public class AccessibilitySettings extends DashboardFragment implements

    private AccessibilitySettingsContentObserver mSettingsContentObserver;

    private FeedbackManager mFeedbackManager;

    private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
            new ArrayMap<>();
    private final List<Preference> mServicePreferences = new ArrayList<>();
@@ -216,6 +210,7 @@ public class AccessibilitySettings extends DashboardFragment implements
        mNeedPreferencesUpdate = false;
        registerContentMonitors();
        registerInputDeviceListener();
        FeedbackMenuController.init(this, SettingsEnums.ACCESSIBILITY);
    }

    @Override
@@ -252,24 +247,6 @@ public class AccessibilitySettings extends DashboardFragment implements
        super.onDestroy();
    }

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        if (getFeedbackManager().isAvailable()) {
            menu.add(Menu.NONE, MENU_ID_SEND_FEEDBACK, Menu.NONE,
                    R.string.accessibility_send_feedback_title);
        }
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (item.getItemId() == MENU_ID_SEND_FEEDBACK) {
            getFeedbackManager().sendFeedback();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.accessibility_settings;
@@ -280,18 +257,6 @@ public class AccessibilitySettings extends DashboardFragment implements
        return TAG;
    }

    @VisibleForTesting
    void setFeedbackManager(FeedbackManager feedbackManager) {
        this.mFeedbackManager = feedbackManager;
    }

    private FeedbackManager getFeedbackManager() {
        if (mFeedbackManager == null) {
            mFeedbackManager = new FeedbackManager(getActivity(), SettingsEnums.ACCESSIBILITY);
        }
        return mFeedbackManager;
    }

    /**
     * Returns the summary for the current state of this accessibilityService.
     *
+15 −47
Original line number Diff line number Diff line
@@ -40,9 +40,6 @@ import android.service.quicksettings.TileService;
import android.text.Html;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
@@ -63,6 +60,7 @@ import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.accessibility.actionbar.FeedbackMenuController;
import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
@@ -94,7 +92,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
    // <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
    private static final String IMG_PREFIX = "R.drawable.";
    private static final String DRAWABLE_FOLDER = "drawable";
    static final int MENU_ID_SEND_FEEDBACK = 0;

    protected TopIntroPreference mTopIntroPreference;
    protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
@@ -108,7 +105,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
    protected Intent mSettingsIntent;
    // The mComponentName maybe null, such as Magnify
    protected ComponentName mComponentName;
    @Nullable private FeedbackManager mFeedbackManager;
    protected CharSequence mFeatureName;
    protected Uri mImageUri;
    protected CharSequence mHtmlDescription;
@@ -142,6 +138,8 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment

        mSettingsContentObserver = new AccessibilitySettingsContentObserver(new Handler());
        registerKeysToObserverCallback(mSettingsContentObserver);

        FeedbackMenuController.init(this, getFeedbackCategory());
    }

    protected void registerKeysToObserverCallback(
@@ -247,24 +245,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
        removeActionBarToggleSwitch();
    }

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        if (getFeedbackManager().isAvailable()) {
            menu.add(Menu.NONE, MENU_ID_SEND_FEEDBACK, Menu.NONE,
                    R.string.accessibility_send_feedback_title);
        }
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (item.getItemId() == MENU_ID_SEND_FEEDBACK) {
            getFeedbackManager().sendFeedback();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public int getDialogMetricsCategory(int dialogId) {
        switch (dialogId) {
@@ -280,6 +260,18 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
        return SettingsEnums.ACCESSIBILITY_SERVICE;
    }

    /**
     * Returns the category of the feedback page.
     *
     * <p>By default, this method returns {@link SettingsEnums#PAGE_UNKNOWN}. This indicates that
     * the feedback category is unknown, and the absence of a feedback menu.
     *
     * @return The feedback category, which is {@link SettingsEnums#PAGE_UNKNOWN} by default.
     */
    protected int getFeedbackCategory() {
        return SettingsEnums.PAGE_UNKNOWN;
    }

    @Override
    public int getHelpResource() {
        return 0;
@@ -785,28 +777,4 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
                super.onCreateRecyclerView(inflater, parent, savedInstanceState);
        return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView);
    }

    @VisibleForTesting
    void setFeedbackManager(FeedbackManager feedbackManager) {
        this.mFeedbackManager = feedbackManager;
    }

    private FeedbackManager getFeedbackManager() {
        if (mFeedbackManager == null) {
            mFeedbackManager = new FeedbackManager(getActivity(), getFeedbackCategory());
        }
        return mFeedbackManager;
    }

    /**
     * Returns the category of the feedback page.
     *
     * <p>By default, this method returns {@link SettingsEnums#PAGE_UNKNOWN}. This indicates that
     * the feedback category is unknown, and the absence of a feedback menu.
     *
     * @return The feedback category, which is {@link SettingsEnums#PAGE_UNKNOWN} by default.
     */
    protected int getFeedbackCategory() {
        return SettingsEnums.PAGE_UNKNOWN;
    }
}
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.settings.accessibility.actionbar;

import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import androidx.annotation.NonNull;

import com.android.settings.R;
import com.android.settings.accessibility.FeedbackManager;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected;

/**
 * A controller that adds feedback menu to any Settings page.
 */
public class FeedbackMenuController implements LifecycleObserver, OnCreateOptionsMenu,
        OnOptionsItemSelected {

    /**
     * The menu item ID for the feedback menu option.
     */
    public static final int MENU_FEEDBACK = Menu.FIRST + 10;

    /**
     * The menu item ID for the feedback menu option.
     */
    private final FeedbackManager mFeedbackManager;

    /**
     * Initializes the FeedbackMenuController for an InstrumentedPreferenceFragment with a provided
     * pade ID.
     *
     * @param host The InstrumentedPreferenceFragment to which the menu controller will be added.
     * @param pageId The page ID used for feedback tracking.
     */
    public static void init(@NonNull InstrumentedPreferenceFragment host, int pageId) {
        host.getSettingsLifecycle().addObserver(
                new FeedbackMenuController(
                        new FeedbackManager(host.getActivity(), pageId)));
    }

    /**
     * Initializes the FeedbackMenuController for an InstrumentedPreferenceFragment with a provided
     * FeedbackManager.
     *
     * @param host The InstrumentedPreferenceFragment to which the menu controller will be added.
     * @param feedbackManager The FeedbackManager to use for handling feedback actions.
     */
    public static void init(@NonNull InstrumentedPreferenceFragment host,
            @NonNull FeedbackManager feedbackManager) {
        host.getSettingsLifecycle().addObserver(
                new FeedbackMenuController(feedbackManager));
    }

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        if (!mFeedbackManager.isAvailable()) {
            return;
        }
        menu.add(Menu.NONE, MENU_FEEDBACK, Menu.NONE, R.string.accessibility_send_feedback_title);
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem menuItem) {
        if (menuItem.getItemId() == MENU_FEEDBACK) {
            mFeedbackManager.sendFeedback();
            return true;
        }
        return false;
    }

    private FeedbackMenuController(@NonNull FeedbackManager feedbackManager) {
        mFeedbackManager = feedbackManager;
    }
}
+0 −62
Original line number Diff line number Diff line
@@ -21,10 +21,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;

@@ -46,8 +43,6 @@ import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuItem;
import android.view.accessibility.AccessibilityManager;

import androidx.fragment.app.Fragment;
@@ -112,7 +107,6 @@ public class AccessibilitySettingsTest {
    private static final String EMPTY_STRING = "";
    private static final String DEFAULT_SUMMARY = "default summary";
    private static final String DEFAULT_DESCRIPTION = "default description";
    private static final String DEFAULT_CATEGORY = "default category";
    private static final String DEFAULT_LABEL = "default label";
    private static final Boolean SERVICE_ENABLED = true;
    private static final Boolean SERVICE_DISABLED = false;
@@ -128,10 +122,6 @@ public class AccessibilitySettingsTest {
    private ShadowAccessibilityManager mShadowAccessibilityManager;
    @Mock
    private LocalBluetoothManager mLocalBluetoothManager;
    @Mock
    private Menu mMenu;
    @Mock
    private MenuItem mMenuItem;

    private ActivityController<SettingsActivity> mActivityController;

@@ -451,58 +441,6 @@ public class AccessibilitySettingsTest {

    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
    public void onCreateOptionsMenu_enableLowVisionGenericFeedback_shouldAddSendFeedbackMenu() {
        setupFragment();
        mFragment.setFeedbackManager(
                new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));

        mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);

        verify(mMenu).add(anyInt(), anyInt(), anyInt(), anyInt());
    }

    @Test
    @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
    public void onCreateOptionsMenu_disableLowVisionGenericFeedback_shouldNotAddSendFeedbackMenu() {
        setupFragment();
        mFragment.setFeedbackManager(
                new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));

        mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);

        verify(mMenu, never()).add(anyInt(), anyInt(), anyInt(), anyInt());
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
    public void onOptionsItemSelected_enableLowVisionGenericFeedback_shouldStartSendFeedback() {
        setupFragment();
        mFragment.setFeedbackManager(
                new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
        when(mMenuItem.getItemId()).thenReturn(AccessibilitySettings.MENU_ID_SEND_FEEDBACK);

        mFragment.onOptionsItemSelected(mMenuItem);

        Intent startedIntent = shadowOf(mFragment.getActivity()).getNextStartedActivity();
        assertThat(startedIntent).isNotNull();
    }

    @Test
    @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
    public void onOptionsItemSelected_disableLowVisionGenericFeedback_shouldNotStartSendFeedback() {
        setupFragment();
        mFragment.setFeedbackManager(
                new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
        when(mMenuItem.getItemId()).thenReturn(AccessibilitySettings.MENU_ID_SEND_FEEDBACK);

        mFragment.onOptionsItemSelected(mMenuItem);

        Intent startedIntent = shadowOf(mFragment.getActivity()).getNextStartedActivity();
        assertThat(startedIntent).isNull();
    }

    @Test
    public void testAccessibilityMenuInSystem_IncludedInInteractionControl() {
        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
+4 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -82,6 +83,8 @@ public class ToggleColorInversionPreferenceFragmentTest {
    private PreferenceManager mPreferenceManager;
    @Mock
    private FragmentActivity mActivity;
    @Mock
    private Resources mResources;

    @Before
    public void setUpTestFragment() {
@@ -93,6 +96,7 @@ public class ToggleColorInversionPreferenceFragmentTest {
        when(mFragment.getContext()).thenReturn(mContext);
        when(mFragment.getActivity()).thenReturn(mActivity);
        when(mActivity.getContentResolver()).thenReturn(mContext.getContentResolver());
        when(mActivity.getResources()).thenReturn(mResources);

        mScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null));
        when(mScreen.findPreference(mFragment.getUseServicePreferenceKey()))
Loading