Add more education tips for widgets. (1/3)

Show Arrow education tip on WidgetsBottomSheet. This tip is shown only
once either in widgets full sheet or bottom sheet.

Test: Tested manually
Bug: 185354491
Change-Id: I6d770ed0906a5cb0f5bcfe6ae0cd327b790cf164
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
index 08635c6..1859bd8 100644
--- a/res/layout/widgets_bottom_sheet.xml
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -21,7 +21,6 @@
     android:layout_height="wrap_content"
     android:paddingTop="16dp"
     android:background="@drawable/widgets_bottom_sheet_background"
-    android:elevation="@dimen/deep_shortcuts_elevation"
     android:layout_gravity="bottom"
     android:theme="?attr/widgetsTheme">
 
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index a6f2b42..4ee365e 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -196,7 +196,6 @@
         parent.addView(this);
         requestLayout();
 
-        post(() -> setY(yCoord - getHeight()));
         post(() -> {
             float halfWidth = getWidth() / 2f;
             float xCoord;
@@ -208,7 +207,9 @@
                 xCoord = arrowXCoord - halfWidth;
             }
             setX(xCoord);
-            findViewById(R.id.arrow).setX(arrowXCoord - xCoord);
+            setY(yCoord - getHeight());
+            View arrowView = findViewById(R.id.arrow);
+            arrowView.setX(arrowXCoord - xCoord - arrowView.getWidth() / 2f);
             requestLayout();
         });
 
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index f7e295e..e6791c3 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -26,6 +26,9 @@
 import android.view.View.OnLongClickListener;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Launcher;
@@ -39,6 +42,7 @@
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.AbstractSlideInView;
+import com.android.launcher3.views.ArrowTipView;
 
 /**
  * Base class for various widgets popup
@@ -47,6 +51,9 @@
         implements OnClickListener, OnLongClickListener, DragSource,
         PopupDataProvider.PopupDataChangeListener {
 
+    protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
+            "launcher.widgets_education_tip_seen";
+
     /* Touch handling related member variables. */
     private Toast mWidgetInstructionToast;
 
@@ -196,4 +203,28 @@
         toast.show();
         return toast;
     }
+
+    /** Shows education tip on top center of {@code view} if view is laid out. */
+    @Nullable
+    protected ArrowTipView showEducationTipOnViewIfPossible(@Nullable View view) {
+        if (view == null || !ViewCompat.isLaidOut(view)) {
+            return null;
+        }
+
+        mActivityContext.getSharedPrefs().edit()
+                .putBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, true).apply();
+        int[] coords = new int[2];
+        view.getLocationOnScreen(coords);
+        ArrowTipView arrowTipView = new ArrowTipView(mActivityContext);
+        return arrowTipView.showAtLocation(
+                getContext().getString(R.string.long_press_widget_to_add),
+                /* arrowXCoord= */coords[0] + view.getWidth() / 2,
+                /* yCoord= */coords[1]);
+    }
+
+    /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */
+    protected boolean hasSeenEducationTip() {
+        return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false)
+                || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 787a2d1..81df100 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -68,12 +68,43 @@
             };
 
     private static final int DEFAULT_CLOSE_DURATION = 200;
+    private static final long EDUCATION_TIP_DELAY_MS = 300;
+
     private ItemInfo mOriginalItemInfo;
     private Rect mInsets;
     private final int mMaxTableHeight;
     private int mMaxHorizontalSpan = 4;
     private Configuration mCurrentConfiguration;
 
+    private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
+            new OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    if (hasSeenEducationTip()) {
+                        removeOnLayoutChangeListener(this);
+                        return;
+                    }
+                    // Widgets are loaded asynchronously, We are adding a delay because we only want
+                    // to show the tip when the widget preview has finished loading and rendering in
+                    // this view.
+                    removeCallbacks(mShowEducationTipTask);
+                    postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
+                }
+            };
+
+    private final Runnable mShowEducationTipTask = () -> {
+        if (hasSeenEducationTip()) {
+            removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
+            return;
+        }
+        View viewForTip = ((ViewGroup) ((TableLayout) findViewById(R.id.widgets_table))
+                                    .getChildAt(0)).getChildAt(0);
+        if (showEducationTipOnViewIfPossible(viewForTip) != null) {
+            removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
+        }
+    };
+
     public WidgetsBottomSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -88,6 +119,9 @@
         // take over the entire view vertically.
         mMaxTableHeight = deviceProfile.inv.numRows * 2 / 3  * deviceProfile.cellHeightPx;
         mCurrentConfiguration = new Configuration(getResources().getConfiguration());
+        if (!hasSeenEducationTip()) {
+            addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 42ff249..145d5cb 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -41,7 +41,6 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import androidx.core.view.ViewCompat;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.DeviceProfile;
@@ -49,11 +48,9 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.views.ArrowTipView;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 import com.android.launcher3.widget.BaseWidgetSheet;
@@ -86,7 +83,6 @@
     // resolution or landscape on phone. This ratio defines the max percentage of content area that
     // the table can display.
     private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
-    private static final String WIDGETS_EDUCATION_TIP_SEEN = "launcher.widgets_education_tip_seen";
 
     private final Rect mInsets = new Rect();
     private final boolean mHasWorkProfile;
@@ -120,9 +116,8 @@
             return;
         }
         View viewForTip = getViewToShowEducationTip();
-        if (viewForTip != null && ViewCompat.isLaidOut(viewForTip)) {
+        if (showEducationTipOnViewIfPossible(viewForTip) != null) {
             removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
-            showEducationTipOnView(viewForTip);
         }
     };
     private final int mTabsHeight;
@@ -618,18 +613,6 @@
         getWindowInsetsController().hide(WindowInsets.Type.ime());
     }
 
-    private void showEducationTipOnView(View view) {
-        mActivityContext.getSharedPrefs().edit()
-                .putBoolean(WIDGETS_EDUCATION_TIP_SEEN, true).apply();
-        int[] coords = new int[2];
-        view.getLocationOnScreen(coords);
-        ArrowTipView arrowTipView = new ArrowTipView(mActivityContext);
-        arrowTipView.showAtLocation(
-                getContext().getString(R.string.long_press_widget_to_add),
-                /* arrowXCoord= */coords[0] + view.getWidth() / 2,
-                /* yCoord= */coords[1]);
-    }
-
     @Nullable private View getViewToShowEducationTip() {
         if (mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getVisibility() == VISIBLE
                 && mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getChildCount() > 0
@@ -658,11 +641,6 @@
         return null;
     }
 
-    private boolean hasSeenEducationTip() {
-        return mActivityContext.getSharedPrefs().getBoolean(WIDGETS_EDUCATION_TIP_SEEN, false)
-                || Utilities.IS_RUNNING_IN_TEST_HARNESS;
-    }
-
     /** A holder class for holding adapters & their corresponding recycler view. */
     private final class AdapterHolder {
         static final int PRIMARY = 0;