Add education tip to widget picker.

A tip is shown on the first widget/shortcut in the recommended table.
If there are no recommended widgets, a tip is shown on first widget
in an expanded header.

There is a delay of few milliseconds, to let the WidgetCells be
completely rendered on screen before getting their location.

Test: Manually tested
Bug: 184920163
Change-Id: I2637e84e7fc467b27888023434e3578a4b8ed4d6
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index a4257a2..5d9a2e2 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -41,6 +41,7 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.view.ViewCompat;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.DeviceProfile;
@@ -51,6 +52,7 @@
 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;
@@ -66,6 +68,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.stream.IntStream;
 
 /**
  * Popup for showing the full list of available widgets
@@ -78,11 +81,13 @@
 
     private static final long DEFAULT_OPEN_DURATION = 267;
     private static final long FADE_IN_DURATION = 150;
+    private static final long EDUCATION_TIP_DELAY_MS = 200;
     private static final float VERTICAL_START_POSITION = 0.3f;
     // The widget recommendation table can easily take over the entire screen on devices with small
     // 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;
@@ -92,6 +97,35 @@
             mCurrentUser.equals(entry.mPkgItem.user);
     private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
             mPrimaryWidgetsFilter.negate();
+    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 = getViewToShowEducationTip();
+        if (viewForTip != null && ViewCompat.isLaidOut(viewForTip)) {
+            removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
+            showEducationTipOnView(viewForTip);
+        }
+    };
     private final int mTabsHeight;
     private final int mWidgetCellHorizontalPadding;
 
@@ -170,6 +204,10 @@
 
         mSearchAndRecommendationViewHolder.mSearchBar.initialize(
                 mLauncher.getPopupDataProvider(), /* searchModeListener= */ this);
+
+        if (!hasSeenEducationTip()) {
+            addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
+        }
     }
 
     @Override
@@ -563,6 +601,49 @@
         mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
     }
 
+    private void showEducationTipOnView(View view) {
+        mLauncher.getSharedPrefs().edit().putBoolean(WIDGETS_EDUCATION_TIP_SEEN, true).apply();
+        int[] coords = new int[2];
+        view.getLocationOnScreen(coords);
+        ArrowTipView arrowTipView = new ArrowTipView(mLauncher);
+        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
+        ) {
+            return ((ViewGroup) mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
+                    .getChildAt(0)).getChildAt(0);
+        }
+
+        AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
+                ? AdapterHolder.SEARCH
+                : mViewPager == null
+                        ? AdapterHolder.PRIMARY
+                        : mViewPager.getCurrentPage());
+        WidgetsRowViewHolder viewHolderForTip =
+                (WidgetsRowViewHolder) IntStream.range(
+                                0, adapterHolder.mWidgetsListAdapter.getItemCount())
+                        .mapToObj(adapterHolder.mWidgetsRecyclerView::
+                                findViewHolderForAdapterPosition)
+                        .filter(viewHolder -> viewHolder instanceof WidgetsRowViewHolder)
+                        .findFirst()
+                        .orElse(null);
+        if (viewHolderForTip != null) {
+            return ((ViewGroup) viewHolderForTip.mTableContainer.getChildAt(0)).getChildAt(0);
+        }
+
+        return null;
+    }
+
+    private boolean hasSeenEducationTip() {
+        return mLauncher.getSharedPrefs().getBoolean(WIDGETS_EDUCATION_TIP_SEEN, false);
+    }
+
     /** A holder class for holding adapters & their corresponding recycler view. */
     private final class AdapterHolder {
         static final int PRIMARY = 0;