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;