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

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

Merge "Fix accessibility settings page did not update the preference state" into sc-dev

parents 2affde32 15df635d
Loading
Loading
Loading
Loading
+53 −16
Original line number Diff line number Diff line
@@ -111,7 +111,7 @@ public class AccessibilitySettings extends DashboardFragment {
        @Override
        public void run() {
            if (getActivity() != null) {
                updateServicePreferences();
                onContentChanged();
            }
        }
    };
@@ -142,7 +142,8 @@ public class AccessibilitySettings extends DashboardFragment {
        }
    };

    private final SettingsContentObserver mSettingsContentObserver;
    @VisibleForTesting
    final SettingsContentObserver mSettingsContentObserver;

    private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
            new ArrayMap<>();
@@ -151,6 +152,9 @@ public class AccessibilitySettings extends DashboardFragment {
    private final Map<ComponentName, PreferenceCategory> mPreBundledServiceComponentToCategoryMap =
            new ArrayMap<>();

    private boolean mNeedPreferencesUpdate = false;
    private boolean mIsForeground = true;

    public AccessibilitySettings() {
        // Observe changes to anything that the shortcut can toggle, so we can reflect updates
        final Collection<AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> features =
@@ -166,7 +170,7 @@ public class AccessibilitySettings extends DashboardFragment {
        mSettingsContentObserver = new SettingsContentObserver(mHandler, shortcutFeatureKeys) {
            @Override
            public void onChange(boolean selfChange, Uri uri) {
                updateAllPreferences();
                onContentChanged();
            }
        };
    }
@@ -181,13 +185,6 @@ public class AccessibilitySettings extends DashboardFragment {
        return R.string.help_uri_accessibility;
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        initializeAllPreferences();
        updateAllPreferences();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
@@ -195,21 +192,36 @@ public class AccessibilitySettings extends DashboardFragment {
                .setFragmentManager(getFragmentManager());
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        initializeAllPreferences();
        updateAllPreferences();
        registerContentMonitors();
    }

    @Override
    public void onStart() {
        if (mNeedPreferencesUpdate) {
            updateAllPreferences();
            mNeedPreferencesUpdate = false;
        }
        mIsForeground = true;
        super.onStart();

        mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
        mSettingsContentObserver.register(getContentResolver());
    }

    @Override
    public void onStop() {
        mSettingsPackageMonitor.unregister();
        mSettingsContentObserver.unregister(getContentResolver());
        mIsForeground = false;
        super.onStop();
    }

    @Override
    public void onDestroy() {
        unregisterContentMonitors();
        super.onDestroy();
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.accessibility_settings;
@@ -283,6 +295,17 @@ public class AccessibilitySettings extends DashboardFragment {
                context.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) == 1;
    }

    @VisibleForTesting
    void onContentChanged() {
        // If the fragment is visible then update preferences immediately, else set the flag then
        // wait for the fragment to show up to update preferences.
        if (mIsForeground) {
            updateAllPreferences();
        } else {
            mNeedPreferencesUpdate = true;
        }
    }

    private void initializeAllPreferences() {
        for (int i = 0; i < CATEGORIES.length; i++) {
            PreferenceCategory prefCategory = findPreference(CATEGORIES[i]);
@@ -290,11 +313,25 @@ public class AccessibilitySettings extends DashboardFragment {
        }
    }

    private void updateAllPreferences() {
    @VisibleForTesting
    void updateAllPreferences() {
        updateSystemPreferences();
        updateServicePreferences();
    }

    private void registerContentMonitors() {
        final Context context = getActivity();

        mSettingsPackageMonitor.register(context, context.getMainLooper(), /* externalStorage= */
                false);
        mSettingsContentObserver.register(getContentResolver());
    }

    private void unregisterContentMonitors() {
        mSettingsPackageMonitor.unregister();
        mSettingsContentObserver.unregister(getContentResolver());
    }

    protected void updateServicePreferences() {
        // Since services category is auto generated we have to do a pass
        // to generate it since services can come and go and then based on
+140 −30
Original line number Diff line number Diff line
@@ -19,35 +19,55 @@ package com.android.settings.accessibility;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static java.util.Collections.singletonList;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;

import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;

import com.android.internal.content.PackageMonitor;
import com.android.settings.R;
import com.android.settings.testutils.XmlTestUtils;
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settingslib.RestrictedPreference;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowAccessibilityManager;
@@ -55,15 +75,14 @@ import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
public class AccessibilitySettingsTest {
    private static final String DUMMY_PACKAGE_NAME = "com.mock.example";
    private static final String DUMMY_CLASS_NAME = DUMMY_PACKAGE_NAME + ".mock_a11y_service";
    private static final ComponentName DUMMY_COMPONENT_NAME = new ComponentName(DUMMY_PACKAGE_NAME,
            DUMMY_CLASS_NAME);
    private static final String PACKAGE_NAME = "com.android.test";
    private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service";
    private static final ComponentName COMPONENT_NAME = new ComponentName(PACKAGE_NAME,
            CLASS_NAME);
    private static final int ON = 1;
    private static final int OFF = 0;
    private static final String EMPTY_STRING = "";
@@ -72,24 +91,35 @@ public class AccessibilitySettingsTest {
    private static final String DEFAULT_LABEL = "default label";
    private static final Boolean SERVICE_ENABLED = true;
    private static final Boolean SERVICE_DISABLED = false;

    private Context mContext;
    private AccessibilitySettings mSettings;
    private ShadowAccessibilityManager mShadowAccessibilityManager;
    private AccessibilityServiceInfo mServiceInfo;
    @Rule
    public final MockitoRule mocks = MockitoJUnit.rule();
    @Spy
    private final Context mContext = ApplicationProvider.getApplicationContext();
    @Spy
    private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo(
            PACKAGE_NAME, CLASS_NAME);
    @Spy
    private final AccessibilitySettings mFragment = new AccessibilitySettings();
    @Mock
    private AccessibilityShortcutInfo mShortcutInfo;
    @Mock
    private FragmentActivity mActivity;
    @Mock
    private ContentResolver mContentResolver;
    @Mock
    private PreferenceManager mPreferenceManager;
    private ShadowAccessibilityManager mShadowAccessibilityManager;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);

        mContext = spy(RuntimeEnvironment.application);
        mSettings = spy(new AccessibilitySettings());
        mServiceInfo = spy(getMockAccessibilityServiceInfo());
        mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext));
        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>());
        doReturn(mContext).when(mSettings).getContext();
        when(mFragment.getContext()).thenReturn(mContext);
        when(mFragment.getActivity()).thenReturn(mActivity);
        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
        when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
        when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext);
        mContext.setTheme(R.style.Theme_AppCompat);
    }

    @Test
@@ -216,11 +246,11 @@ public class AccessibilitySettingsTest {

    @Test
    public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() {
        final String key = DUMMY_COMPONENT_NAME.flattenToString();
        final String key = COMPONENT_NAME.flattenToString();
        final AccessibilitySettings.RestrictedPreferenceHelper helper =
                new AccessibilitySettings.RestrictedPreferenceHelper(mContext);
        final List<AccessibilityServiceInfo> infoList = new ArrayList<>(
                Collections.singletonList(mServiceInfo));
                singletonList(mServiceInfo));

        final List<RestrictedPreference> preferenceList =
                helper.createAccessibilityServicePreferenceList(infoList);
@@ -231,12 +261,12 @@ public class AccessibilitySettingsTest {

    @Test
    public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() {
        final String key = DUMMY_COMPONENT_NAME.flattenToString();
        final String key = COMPONENT_NAME.flattenToString();
        final AccessibilitySettings.RestrictedPreferenceHelper helper =
                new AccessibilitySettings.RestrictedPreferenceHelper(mContext);
        setMockAccessibilityShortcutInfo(mShortcutInfo);
        final List<AccessibilityShortcutInfo> infoList = new ArrayList<>(
                Collections.singletonList(mShortcutInfo));
                singletonList(mShortcutInfo));

        final List<RestrictedPreference> preferenceList =
                helper.createAccessibilityActivityPreferenceList(infoList);
@@ -245,21 +275,94 @@ public class AccessibilitySettingsTest {
        assertThat(preference.getKey()).isEqualTo(key);
    }

    private AccessibilityServiceInfo getMockAccessibilityServiceInfo() {
    @Test
    @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
    public void onCreate_haveRegisterToSpecificUrisAndActions() {
        final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
        final IntentFilter intentFilter;
        mFragment.onAttach(mContext);

        mFragment.onCreate(Bundle.EMPTY);

        verify(mContentResolver).registerContentObserver(
                eq(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS)),
                anyBoolean(),
                any(SettingsContentObserver.class));
        verify(mContentResolver).registerContentObserver(eq(Settings.Secure.getUriFor(
                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)), anyBoolean(),
                any(SettingsContentObserver.class));
        verify(mActivity, atLeast(1)).registerReceiver(any(PackageMonitor.class), captor.capture(),
                isNull(), any());
        intentFilter = captor.getAllValues().get(/* first time */ 0);
        assertThat(intentFilter.hasAction(Intent.ACTION_PACKAGE_ADDED)).isTrue();
        assertThat(intentFilter.hasAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue();
    }

    @Test
    @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
    public void onDestroy_unregisterObserverAndReceiver() {
        setupFragment();
        mFragment.onPause();
        mFragment.onStop();

        mFragment.onDestroy();

        verify(mContentResolver).unregisterContentObserver(any(SettingsContentObserver.class));
        verify(mActivity).unregisterReceiver(any(PackageMonitor.class));

    }

    @Test
    @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
    public void onContentChanged_updatePreferenceInForeground_preferenceUpdated() {
        setupFragment();
        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
                singletonList(mServiceInfo));

        mFragment.onContentChanged();

        RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference(
                COMPONENT_NAME.flattenToString());

        assertThat(preference).isNotNull();

    }

    @Test
    @Config(shadows = {ShadowFragment.class, ShadowUserManager.class})
    public void onContentChanged_updatePreferenceInBackground_preferenceUpdated() {
        setupFragment();
        mFragment.onPause();
        mFragment.onStop();

        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
                singletonList(mServiceInfo));

        mFragment.onContentChanged();
        mFragment.onStart();

        RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference(
                COMPONENT_NAME.flattenToString());

        assertThat(preference).isNotNull();

    }

    private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName,
            String className) {
        final ApplicationInfo applicationInfo = new ApplicationInfo();
        final ServiceInfo serviceInfo = new ServiceInfo();
        applicationInfo.packageName = DUMMY_PACKAGE_NAME;
        serviceInfo.packageName = DUMMY_PACKAGE_NAME;
        serviceInfo.name = DUMMY_CLASS_NAME;
        applicationInfo.packageName = packageName;
        serviceInfo.packageName = packageName;
        serviceInfo.name = className;
        serviceInfo.applicationInfo = applicationInfo;

        final ResolveInfo resolveInfo = new ResolveInfo();
        resolveInfo.serviceInfo = serviceInfo;

        try {
            final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo,
                    mContext);
            info.setComponentName(DUMMY_COMPONENT_NAME);
            info.setComponentName(new ComponentName(PACKAGE_NAME, CLASS_NAME));
            return info;
        } catch (XmlPullParserException | IOException e) {
            // Do nothing
@@ -274,11 +377,18 @@ public class AccessibilitySettingsTest {
        when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL);
        when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY);
        when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION);
        when(mockInfo.getComponentName()).thenReturn(DUMMY_COMPONENT_NAME);
        when(mockInfo.getComponentName()).thenReturn(COMPONENT_NAME);
    }

    private void setInvisibleToggleFragmentType(AccessibilityServiceInfo info) {
        info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
        info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
    }

    private void setupFragment() {
        mFragment.onAttach(mContext);
        mFragment.onCreate(Bundle.EMPTY);
        mFragment.onStart();
        mFragment.onResume();
    }
}