Create tooltip for notifying auto-adding the font scaling tile

1. Add string for the content of tooltip.
2. Show the tooltip if needed: the tooltip will only be shown once when users change the font size from the Settings page for the first time.
3. Since the layout shown on the screen will be recreated after font size changes, we need to save the state of the tooltip popup window to check if we need to reshow it in displayPreference.

Bug: 269679768
Test: Manually - attach videos to the bug
Test: make RunSettingsRoboTests ROBOTEST_FILTER=PreviewSizeSeekBarControllerTest
Change-Id: I1b6c5fdbd74c7a868cf42bd21d2cdb1052c0bbe6
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) {