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

Commit 5356e0c0 authored by menghanli's avatar menghanli
Browse files

Refactor CaptionAppearanceFragment to improve maintainability (4/n)

Root cause: There is a bunch of different logic of preferences in CaptionAppearanceFragment. It’s hard to implement new features and hard to maintain and hard to be testable.
Solution: Move out preset preference logic of CaptionAppearanceFragment into controllers to reduce the complexity of the relationship between preference and fragment.

Bug: 197695932
Test: make RunSettingsRoboTests ROBOTEST_FILTER=CaptionPresetControllerTest CaptionAppearanceFragmentTest
Change-Id: I5409c1e8a6bdfc633abc304d8cf800ea0943de78
parent 16fbfe4e
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -38,7 +38,8 @@

    <com.android.settings.accessibility.PresetPreference
        android:key="captioning_preset"
        android:title="@string/captioning_preset"/>
        android:title="@string/captioning_preset"
        settings:controller="com.android.settings.accessibility.CaptionPresetController"/>

    <PreferenceCategory
        android:key="custom"
+40 −67
Original line number Diff line number Diff line
@@ -17,41 +17,42 @@
package com.android.settings.accessibility;

import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;

import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceCategory;

import com.android.settings.R;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;

import java.util.Arrays;
import java.util.List;

/** Settings fragment containing font style of captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class CaptionAppearanceFragment extends DashboardFragment
        implements OnValueChangedListener {
public class CaptionAppearanceFragment extends DashboardFragment {

    private static final String TAG = "CaptionAppearanceFragment";
    private static final String PREF_PRESET = "captioning_preset";
    private static final String PREF_CUSTOM = "custom";

    @VisibleForTesting
    static final String PREF_CUSTOM = "custom";
    @VisibleForTesting
    static final List<String> CAPTIONING_FEATURE_KEYS = Arrays.asList(
            Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET
    );

    private final Handler mHandler = new Handler(Looper.getMainLooper());
    @VisibleForTesting
    AccessibilitySettingsContentObserver mSettingsContentObserver;
    private CaptioningManager mCaptioningManager;
    private CaptionHelper mCaptionHelper;

    // Standard options.
    private PresetPreference mPreset;

    // Custom options.
    private PreferenceCategory mCustom;

    private boolean mShowingCustom;

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.ACCESSIBILITY_CAPTION_APPEARANCE;
@@ -60,68 +61,34 @@ public class CaptionAppearanceFragment extends DashboardFragment
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        super.onCreatePreferences(savedInstanceState, rootKey);

        mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
        mCaptionHelper = new CaptionHelper(getContext());

        initializeAllPreferences();
        updateAllPreferences();
        mCaptioningManager = getContext().getSystemService(CaptioningManager.class);
        mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
        mSettingsContentObserver.registerKeysToObserverCallback(CAPTIONING_FEATURE_KEYS,
                key -> refreshShowingCustom());
        mCustom = findPreference(PREF_CUSTOM);
        refreshShowingCustom();
        installUpdateListeners();
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.captioning_appearance;
    public void onStart() {
        super.onStart();
        mSettingsContentObserver.register(getContext().getContentResolver());
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }

    private void initializeAllPreferences() {

        final Resources res = getResources();
        final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values);
        final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles);
        mPreset = (PresetPreference) findPreference(PREF_PRESET);
        mPreset.setValues(presetValues);
        mPreset.setTitles(presetTitles);

        mCustom = (PreferenceCategory) findPreference(PREF_CUSTOM);
        mShowingCustom = true;
    public void onStop() {
        super.onStop();
        getContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
    }

    private void installUpdateListeners() {
        mPreset.setOnValueChangedListener(this);
    }

    private void updateAllPreferences() {
        final int preset = mCaptioningManager.getRawUserStyle();
        mPreset.setValue(preset);
    }

    private void refreshShowingCustom() {
        final boolean customPreset =
                mPreset.getValue() == CaptioningManager.CaptionStyle.PRESET_CUSTOM;
        if (!customPreset && mShowingCustom) {
            getPreferenceScreen().removePreference(mCustom);
            mShowingCustom = false;
        } else if (customPreset && !mShowingCustom) {
            getPreferenceScreen().addPreference(mCustom);
            mShowingCustom = true;
        }
    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.captioning_appearance;
    }

    @Override
    public void onValueChanged(ListDialogPreference preference, int value) {
        final ContentResolver cr = getActivity().getContentResolver();
        if (mPreset == preference) {
            Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, value);
            refreshShowingCustom();
        }
        mCaptionHelper.setEnabled(true);
    protected String getLogTag() {
        return TAG;
    }

    @Override
@@ -129,6 +96,12 @@ public class CaptionAppearanceFragment extends DashboardFragment
        return R.string.help_url_caption;
    }

    private void refreshShowingCustom() {
        final boolean isCustomPreset =
                mCaptioningManager.getRawUserStyle() == CaptionStyle.PRESET_CUSTOM;
        mCustom.setVisible(isCustomPreset);
    }

    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
            new BaseSearchIndexProvider(R.xml.captioning_appearance);
}
+15 −0
Original line number Diff line number Diff line
@@ -186,4 +186,19 @@ public class CaptionHelper {
        final CaptionStyle attrs = CaptionStyle.getCustomStyle(mContentResolver);
        return attrs.edgeType;
    }

    /**
     * Sets the caption raw user style.
     *
     * @param type The caption raw user style
     */
    public void setRawUserStyle(int type) {
        Settings.Secure.putInt(mContentResolver,
                Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, type);
    }

    /** Returns the caption raw user style.*/
    public int getRawUserStyle() {
        return mCaptioningManager.getRawUserStyle();
    }
}
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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;

import android.content.Context;
import android.content.res.Resources;

import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;

/** Preference controller for caption preset. */
public class CaptionPresetController extends BasePreferenceController
        implements ListDialogPreference.OnValueChangedListener {

    private final CaptionHelper mCaptionHelper;

    public CaptionPresetController(Context context, String preferenceKey) {
        super(context, preferenceKey);
        mCaptionHelper = new CaptionHelper(context);
    }

    @Override
    public int getAvailabilityStatus() {
        return AVAILABLE;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        final PresetPreference preference = screen.findPreference(getPreferenceKey());
        final Resources res = mContext.getResources();
        final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values);
        final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles);
        preference.setTitles(presetTitles);
        preference.setValues(presetValues);
        final int preset = mCaptionHelper.getRawUserStyle();
        preference.setValue(preset);
        preference.setOnValueChangedListener(this);
    }

    @Override
    public void onValueChanged(ListDialogPreference preference, int value) {
        mCaptionHelper.setRawUserStyle(value);
        mCaptionHelper.setEnabled(true);
    }
}
+159 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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;

import static com.google.common.truth.Truth.assertThat;

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 android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager.CaptionStyle;

import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;

import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.testutils.XmlTestUtils;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.util.ReflectionHelpers;

import java.util.List;

/** Tests for {@link CaptionAppearanceFragment}. */
@RunWith(RobolectricTestRunner.class)
public class CaptionAppearanceFragmentTest {

    @Rule
    public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Mock
    private SettingsActivity mActivity;
    @Mock
    private PreferenceScreen mScreen;
    @Mock
    private PreferenceManager mPreferenceManager;
    @Mock
    private ContentResolver mContentResolver;
    @Mock
    private PreferenceCategory mCustomPref;
    @Spy
    private Context mContext = ApplicationProvider.getApplicationContext();
    private TestCaptionAppearanceFragment mFragment;

    @Before
    public void setUp() {
        mFragment = spy(new TestCaptionAppearanceFragment());
        doReturn(mActivity).when(mFragment).getActivity();
        doReturn(mContext).when(mFragment).getContext();
        doReturn(mCustomPref).when(mFragment).findPreference(mFragment.PREF_CUSTOM);
        when(mPreferenceManager.getPreferenceScreen()).thenReturn(mScreen);
        ReflectionHelpers.setField(mFragment, "mPreferenceManager", mPreferenceManager);
    }

    @Test
    public void onCreatePreferences_shouldPreferenceIsInvisible() {
        mFragment.onAttach(mContext);

        mFragment.onCreatePreferences(Bundle.EMPTY, /* rootKey */ null);

        verify(mCustomPref).setVisible(false);
    }

    @Test
    public void onCreatePreferences_customValue_shouldPreferenceIsVisible() {
        Settings.Secure.putInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, CaptionStyle.PRESET_CUSTOM);
        mFragment.onAttach(mContext);

        mFragment.onCreatePreferences(Bundle.EMPTY, /* rootKey */ null);

        verify(mCustomPref).setVisible(true);
    }

    @Test
    public void onStart_registerSpecificContentObserverForSpecificKeys() {
        when(mContext.getContentResolver()).thenReturn(mContentResolver);
        mFragment.onAttach(mContext);
        mFragment.onCreatePreferences(Bundle.EMPTY, /* rootKey */ null);

        mFragment.onStart();

        for (String key : mFragment.CAPTIONING_FEATURE_KEYS) {
            verify(mContentResolver).registerContentObserver(Settings.Secure.getUriFor(key),
                    /* notifyForDescendants= */ false, mFragment.mSettingsContentObserver);
        }
    }

    @Test
    public void onStop_unregisterContentObserver() {
        when(mContext.getContentResolver()).thenReturn(mContentResolver);
        mFragment.onAttach(mContext);
        mFragment.onCreatePreferences(Bundle.EMPTY, /* rootKey */ null);
        mFragment.onStart();

        mFragment.onStop();

        verify(mContentResolver).unregisterContentObserver(mFragment.mSettingsContentObserver);
    }

    @Test
    public void getMetricsCategory_returnsCorrectCategory() {
        assertThat(mFragment.getMetricsCategory()).isEqualTo(
                SettingsEnums.ACCESSIBILITY_CAPTION_APPEARANCE);
    }

    @Test
    public void getLogTag_returnsCorrectTag() {
        assertThat(mFragment.getLogTag()).isEqualTo("CaptionAppearanceFragment");
    }

    @Test
    public void getNonIndexableKeys_existInXmlLayout() {
        final List<String> niks = CaptionAppearanceFragment.SEARCH_INDEX_DATA_PROVIDER
                .getNonIndexableKeys(mContext);
        final List<String> keys = XmlTestUtils.getKeysFromPreferenceXml(mContext,
                R.xml.captioning_appearance);

        assertThat(keys).containsAtLeastElementsIn(niks);
    }

    private static class TestCaptionAppearanceFragment extends CaptionAppearanceFragment {

        @Override
        public int getPreferenceScreenResId() {
            return R.xml.placeholder_prefs;
        }
    }
}
Loading