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

Commit 6800d9f6 authored by Candice Lo's avatar Candice Lo Committed by Android (Google) Code Review
Browse files

Merge "Create tooltip for notifying auto-adding the font scaling tile" into udc-dev

parents a1da0b8d b132ada1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -4595,6 +4595,8 @@
    <string name="accessibility_one_handed_mode_auto_added_qs_tooltip_content">One-handed mode added to Quick Settings. Swipe down to turn it on or off anytime.</string>
    <!-- Used in the One-hand mode settings to show quick settings tooltip. [CHAR LIMIT=NONE] -->
    <string name="accessibility_one_handed_mode_qs_tooltip_content">You can also add one-handed mode to Quick Settings from the top of your screen</string>
    <!-- Used in the font size settings to show quick settings tooltip for auto-added feature. [CHAR LIMIT=NONE] -->
    <string name="accessibility_font_scaling_auto_added_qs_tooltip_content">Font size added to Quick Settings. Swipe down to change the font size anytime.</string>
    <!-- Used in the accessibility action for accessibility quick settings tooltip to dismiss. [CHAR LIMIT=NONE] -->
    <string name="accessibility_quick_settings_tooltip_dismiss">Dismiss</string>
    <!-- Used in the Color correction settings screen to control turning on/off the feature entirely [CHAR LIMIT=60] -->
+85 −2
Original line number Diff line number Diff line
@@ -16,14 +16,22 @@

package com.android.settings.accessibility;

import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.widget.SeekBar;

import androidx.annotation.NonNull;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.widget.LabeledSeekBarPreference;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreate;
import com.android.settingslib.core.lifecycle.events.OnDestroy;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;

import java.util.Optional;

@@ -31,12 +39,19 @@ import java.util.Optional;
 * The controller of {@link LabeledSeekBarPreference} that listens to display size and font size
 * settings changes and updates preview size threshold smoothly.
 */
class PreviewSizeSeekBarController extends BasePreferenceController implements
        TextReadingResetController.ResetStateListener {
abstract class PreviewSizeSeekBarController extends BasePreferenceController implements
        TextReadingResetController.ResetStateListener, LifecycleObserver, OnCreate,
        OnDestroy, OnSaveInstanceState {
    private final PreviewSizeData<? extends Number> mSizeData;
    private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
    private boolean mSeekByTouch;
    private Optional<ProgressInteractionListener> mInteractionListener = Optional.empty();
    private LabeledSeekBarPreference mSeekBarPreference;
    private int mLastProgress;
    private boolean mNeedsQSTooltipReshow = false;
    private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
    private final Handler mHandler;


    private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
            new SeekBar.OnSeekBarChangeListener() {
@@ -54,6 +69,7 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements

                    if (!mSeekByTouch) {
                        interactionListener.onProgressChanged();
                        onProgressFinalized();
                    }
                }

@@ -67,6 +83,7 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements
                    mSeekByTouch = false;

                    mInteractionListener.ifPresent(ProgressInteractionListener::onEndTrackingTouch);
                    onProgressFinalized();
                }
            };

@@ -74,6 +91,30 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements
            @NonNull PreviewSizeData<? extends Number> sizeData) {
        super(context, preferenceKey);
        mSizeData = sizeData;
        mHandler = new Handler(context.getMainLooper());
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Restore the tooltip.
        if (savedInstanceState != null
                && savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
            mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
        }
    }

    @Override
    public void onDestroy() {
        // remove runnables in the queue.
        mHandler.removeCallbacksAndMessages(null);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
        if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
            outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
        }
    }

    void setInteractionListener(ProgressInteractionListener interactionListener) {
@@ -91,11 +132,15 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements

        final int dataSize = mSizeData.getValues().size();
        final int initialIndex = mSizeData.getInitialIndex();
        mLastProgress = initialIndex;
        mSeekBarPreference = screen.findPreference(getPreferenceKey());
        mSeekBarPreference.setMax(dataSize - 1);
        mSeekBarPreference.setProgress(initialIndex);
        mSeekBarPreference.setContinuousUpdates(true);
        mSeekBarPreference.setOnSeekBarChangeListener(mSeekBarChangeListener);
        if (mNeedsQSTooltipReshow) {
            mHandler.post(this::showQuickSettingsTooltipIfNeeded);
        }
    }

    @Override
@@ -108,6 +153,44 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements
        mInteractionListener.ifPresent(ProgressInteractionListener::onProgressChanged);
    }

    private void onProgressFinalized() {
        // Using progress in SeekBarPreference since the progresses in
        // SeekBarPreference and seekbar are not always the same.
        // See {@link androidx.preference.Preference#callChangeListener(Object)}
        int seekBarPreferenceProgress = mSeekBarPreference.getProgress();
        if (seekBarPreferenceProgress != mLastProgress) {
            showQuickSettingsTooltipIfNeeded();
            mLastProgress = seekBarPreferenceProgress;
        }
    }

    private void showQuickSettingsTooltipIfNeeded() {
        final ComponentName tileComponentName = getTileComponentName();
        if (tileComponentName == null) {
            // Returns if no tile service assigned.
            return;
        }

        if (!mNeedsQSTooltipReshow && AccessibilityQuickSettingUtils.hasValueInSharedPreferences(
                mContext, tileComponentName)) {
            // Returns if quick settings tooltip only show once.
            return;
        }

        mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext);
        mTooltipWindow.setup(getTileTooltipContent(),
                R.drawable.accessibility_auto_added_qs_tooltip_illustration);
        mTooltipWindow.showAtTopCenter(mSeekBarPreference.getSeekbar());
        AccessibilityQuickSettingUtils.optInValueToSharedPreferences(mContext, tileComponentName);
        mNeedsQSTooltipReshow = false;
    }

    /** Returns the accessibility Quick Settings tile component name. */
    abstract ComponentName getTileComponentName();

    /** Returns accessibility Quick Settings tile tooltip content. */
    abstract CharSequence getTileTooltipContent();


    /**
     * Interface for callbacks when users interact with the seek bar.
+26 −2
Original line number Diff line number Diff line
@@ -16,11 +16,13 @@

package com.android.settings.accessibility;

import static com.android.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_COMPONENT_NAME;
import static com.android.settings.accessibility.TextReadingResetController.ResetStateListener;

import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
@@ -156,12 +158,34 @@ public class TextReadingPreferenceFragment extends DashboardFragment {
        controllers.add(mPreviewController);

        final PreviewSizeSeekBarController fontSizeController = new PreviewSizeSeekBarController(
                context, FONT_SIZE_KEY, fontSizeData);
                context, FONT_SIZE_KEY, fontSizeData) {
            @Override
            ComponentName getTileComponentName() {
                return FONT_SIZE_COMPONENT_NAME;
            }

            @Override
            CharSequence getTileTooltipContent() {
                return context.getText(
                        R.string.accessibility_font_scaling_auto_added_qs_tooltip_content);
            }
        };
        fontSizeController.setInteractionListener(mPreviewController);
        getSettingsLifecycle().addObserver(fontSizeController);
        controllers.add(fontSizeController);

        final PreviewSizeSeekBarController displaySizeController = new PreviewSizeSeekBarController(
                context, DISPLAY_SIZE_KEY, displaySizeData);
                context, DISPLAY_SIZE_KEY, displaySizeData) {
            @Override
            ComponentName getTileComponentName() {
                return null;
            }

            @Override
            CharSequence getTileTooltipContent() {
                return null;
            }
        };
        displaySizeController.setInteractionListener(mPreviewController);
        controllers.add(displaySizeController);

+10 −4
Original line number Diff line number Diff line
@@ -63,6 +63,8 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
    private OnPreferenceChangeListener mStopListener;
    private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener;

    private SeekBar mSeekBar;

    public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {

@@ -104,6 +106,10 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
                com.android.internal.R.attr.seekBarPreferenceStyle), 0);
    }

    public SeekBar getSeekbar() {
        return mSeekBar;
    }

    @Override
    public void onBindViewHolder(PreferenceViewHolder holder) {
        super.onBindViewHolder(holder);
@@ -133,19 +139,19 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
        final boolean isValidTextResIdExist = mTextStartId > 0 || mTextEndId > 0;
        labelFrame.setVisibility(isValidTextResIdExist ? View.VISIBLE : View.GONE);

        final SeekBar seekBar = (SeekBar) holder.findViewById(com.android.internal.R.id.seekbar);
        mSeekBar = (SeekBar) holder.findViewById(com.android.internal.R.id.seekbar);
        if (mTickMarkId != 0) {
            final Drawable tickMark = getContext().getDrawable(mTickMarkId);
            seekBar.setTickMark(tickMark);
            mSeekBar.setTickMark(tickMark);
        }

        final ViewGroup iconStartFrame = (ViewGroup) holder.findViewById(R.id.icon_start_frame);
        final ImageView iconStartView = (ImageView) holder.findViewById(R.id.icon_start);
        updateIconStartIfNeeded(iconStartFrame, iconStartView, seekBar);
        updateIconStartIfNeeded(iconStartFrame, iconStartView, mSeekBar);

        final ViewGroup iconEndFrame = (ViewGroup) holder.findViewById(R.id.icon_end_frame);
        final ImageView iconEndView = (ImageView) holder.findViewById(R.id.icon_end);
        updateIconEndIfNeeded(iconEndFrame, iconEndView, seekBar);
        updateIconEndIfNeeded(iconEndFrame, iconEndView, mSeekBar);
    }

    public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) {
+118 −5
Original line number Diff line number Diff line
@@ -16,29 +16,45 @@

package com.android.settings.accessibility;

import static com.android.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_COMPONENT_NAME;

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

import static org.mockito.ArgumentMatchers.anyString;
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.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.PopupWindow;
import android.widget.SeekBar;

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

import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settings.testutils.shadow.ShadowInteractionJankMonitor;
import com.android.settings.widget.LabeledSeekBarPreference;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowApplication;

/**
 * Tests for {@link PreviewSizeSeekBarController}.
@@ -47,30 +63,67 @@ import org.robolectric.annotation.Config;
@Config(shadows = {ShadowInteractionJankMonitor.class})
public class PreviewSizeSeekBarControllerTest {
    private static final String FONT_SIZE_KEY = "font_size";
    private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
    @Spy
    private final Context mContext = ApplicationProvider.getApplicationContext();
    private PreviewSizeSeekBarController mSeekBarController;
    private FontSizeData mFontSizeData;
    private LabeledSeekBarPreference mSeekBarPreference;

    @Mock
    private PreferenceScreen mPreferenceScreen;
    private TestFragment mFragment;
    private PreferenceViewHolder mHolder;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private PreferenceManager mPreferenceManager;

    @Mock
    private PreviewSizeSeekBarController.ProgressInteractionListener mInteractionListener;

    private static PopupWindow getLatestPopupWindow() {
        final ShadowApplication shadowApplication =
                Shadow.extract(ApplicationProvider.getApplicationContext());
        return shadowApplication.getLatestPopupWindow();
    }

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

        mFontSizeData = new FontSizeData(mContext);
        mContext.setTheme(R.style.Theme_AppCompat);
        mFragment = spy(new TestFragment());
        when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
        when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext);
        when(mFragment.getContext()).thenReturn(mContext);
        mPreferenceScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null));
        when(mPreferenceScreen.getPreferenceManager()).thenReturn(mPreferenceManager);
        doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen();
        mSeekBarPreference = spy(new LabeledSeekBarPreference(mContext, /* attrs= */ null));
        mSeekBarPreference.setKey(FONT_SIZE_KEY);

        mSeekBarController =
                new PreviewSizeSeekBarController(mContext, FONT_SIZE_KEY, mFontSizeData);
        LayoutInflater inflater = LayoutInflater.from(mContext);
        mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate(
                R.layout.preference_labeled_slider, null));
        mSeekBarPreference.onBindViewHolder(mHolder);

        mSeekBarPreference = spy(new LabeledSeekBarPreference(mContext, /* attrs= */ null));
        when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference);

        mFontSizeData = new FontSizeData(mContext);
        mSeekBarController =
                new PreviewSizeSeekBarController(mContext, FONT_SIZE_KEY, mFontSizeData) {
                    @Override
                    ComponentName getTileComponentName() {
                        return FONT_SIZE_COMPONENT_NAME;
                    }

                    @Override
                    CharSequence getTileTooltipContent() {
                        return mContext.getText(
                                R.string.accessibility_font_scaling_auto_added_qs_tooltip_content);
                    }
                };
        mSeekBarController.setInteractionListener(mInteractionListener);
        when(mPreferenceScreen.findPreference(mSeekBarController.getPreferenceKey())).thenReturn(
                mSeekBarPreference);
    }

    @Test
@@ -123,4 +176,64 @@ public class PreviewSizeSeekBarControllerTest {

        verify(mInteractionListener).notifyPreferenceChanged();
    }

    @Test
    public void onProgressChanged_showTooltipView() {
        mSeekBarController.displayPreference(mPreferenceScreen);

        // Simulate changing the progress for the first time
        int newProgress = (mSeekBarPreference.getProgress() != 0) ? 0 : mSeekBarPreference.getMax();
        mSeekBarPreference.setProgress(newProgress);
        mSeekBarPreference.onProgressChanged(new SeekBar(mContext),
                newProgress,
                /* fromUser= */ false);

        assertThat(getLatestPopupWindow().isShowing()).isTrue();
    }

    @Test
    public void onProgressChanged_tooltipViewHasBeenShown_notShowTooltipView() {
        mSeekBarController.displayPreference(mPreferenceScreen);
        // Simulate changing the progress for the first time
        int newProgress = (mSeekBarPreference.getProgress() != 0) ? 0 : mSeekBarPreference.getMax();
        mSeekBarPreference.setProgress(newProgress);
        mSeekBarPreference.onProgressChanged(new SeekBar(mContext),
                newProgress,
                /* fromUser= */ false);
        getLatestPopupWindow().dismiss();

        // Simulate progress changing for the second time
        newProgress = (mSeekBarPreference.getProgress() != 0) ? 0 : mSeekBarPreference.getMax();
        mSeekBarPreference.setProgress(newProgress);
        mSeekBarPreference.onProgressChanged(new SeekBar(mContext),
                newProgress,
                /* fromUser= */ false);

        assertThat(getLatestPopupWindow().isShowing()).isFalse();
    }

    @Test
    @Config(shadows = ShadowFragment.class)
    public void restoreValueFromSavedInstanceState_showTooltipView() {
        final Bundle savedInstanceState = new Bundle();
        savedInstanceState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
        mSeekBarController.onCreate(savedInstanceState);

        mSeekBarController.displayPreference(mPreferenceScreen);

        assertThat(getLatestPopupWindow().isShowing()).isTrue();
    }

    private static class TestFragment extends SettingsPreferenceFragment {

        @Override
        protected boolean shouldSkipForInitialSUW() {
            return false;
        }

        @Override
        public int getMetricsCategory() {
            return 0;
        }
    }
}