Merge "Create tooltip for notifying auto-adding the font scaling tile" into udc-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1f1ecc8..6062046 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -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] -->
diff --git a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
index 851797e..089dc7b 100644
--- a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
+++ b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
@@ -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 @@
* 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 @@
if (!mSeekByTouch) {
interactionListener.onProgressChanged();
+ onProgressFinalized();
}
}
@@ -67,6 +83,7 @@
mSeekByTouch = false;
mInteractionListener.ifPresent(ProgressInteractionListener::onEndTrackingTouch);
+ onProgressFinalized();
}
};
@@ -74,6 +91,30 @@
@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 @@
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 @@
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.
diff --git a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
index b35a5fe..97a9071 100644
--- a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
@@ -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 @@
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);
diff --git a/src/com/android/settings/widget/LabeledSeekBarPreference.java b/src/com/android/settings/widget/LabeledSeekBarPreference.java
index 5d10116..6300bd3 100644
--- a/src/com/android/settings/widget/LabeledSeekBarPreference.java
+++ b/src/com/android/settings/widget/LabeledSeekBarPreference.java
@@ -63,6 +63,8 @@
private OnPreferenceChangeListener mStopListener;
private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener;
+ private SeekBar mSeekBar;
+
public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
@@ -104,6 +106,10 @@
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 @@
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) {
diff --git a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
index 52ccb37..6b0f5c0 100644
--- a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
@@ -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 @@
@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);
-
- mSeekBarController =
- new PreviewSizeSeekBarController(mContext, FONT_SIZE_KEY, mFontSizeData);
-
+ 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);
+
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate(
+ R.layout.preference_labeled_slider, null));
+ mSeekBarPreference.onBindViewHolder(mHolder);
+
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 @@
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;
+ }
+ }
}