Merge changes I5d1c9e4b,I08154c94 into sc-dev

* changes:
  Flip ArrowTipView when it goes beyond screen height.
  Add more education tips for widgets. (3/3)
diff --git a/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml
index aee00a9..9a6f8c3 100644
--- a/res/layout/arrow_toast.xml
+++ b/res/layout/arrow_toast.xml
@@ -28,13 +28,14 @@
         android:padding="16dp"
         android:background="@drawable/arrow_toast_rounded_background"
         android:elevation="2dp"
+        android:outlineProvider="none"
         android:textColor="@color/arrow_tip_view_content"
         android:textSize="14sp"/>
 
     <View
         android:id="@+id/arrow"
         android:elevation="2dp"
+        android:outlineProvider="none"
         android:layout_width="@dimen/arrow_toast_arrow_width"
-        android:layout_height="8dp"
-        android:layout_marginTop="-2dp"/>
+        android:layout_height="10dp"/>
 </merge>
diff --git a/res/values-v28/dimens.xml b/res/values-v28/dimens.xml
index ffa8cc4..3f118cd 100644
--- a/res/values-v28/dimens.xml
+++ b/res/values-v28/dimens.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index caeb0b4..06bdd49 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -61,6 +61,7 @@
     <dimen name="widget_reconfigure_button_padding">6dp</dimen>
     <dimen name="widget_reconfigure_button_margin">32dp</dimen>
     <dimen name="widget_reconfigure_button_size">36dp</dimen>
+    <dimen name="widget_reconfigure_tip_top_margin">16dp</dimen>
 
 <!-- Fast scroll -->
     <dimen name="fastscroll_track_min_width">6dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 913763c..2a27828 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -105,6 +105,11 @@
     <!-- Dialog text. This dialog lets a user know how they can use widgets on their phone.
          [CHAR_LIMIT=NONE] -->
     <string name="widget_education_content">To get info without opening apps, you can add widgets to your Home screen</string>
+
+    <!-- Text on an educational tip on widget informing users that they can change widget settings.
+         [CHAR_LIMIT=NONE] -->
+    <string name="reconfigurable_widget_education_tip">Tap to change widget settings</string>
+
     <!-- Text on the button that closes the education dialog about widgets. [CHAR_LIMIT=50] -->
     <string name="widget_education_close_button">Got it</string>
 
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 74ac8c2..9e21e1a 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -24,12 +24,16 @@
 import android.widget.ImageButton;
 import android.widget.ImageView;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
+
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.views.ArrowTipView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.util.WidgetSizes;
@@ -42,6 +46,8 @@
     private static final float DIMMED_HANDLE_ALPHA = 0f;
     private static final float RESIZE_THRESHOLD = 0.66f;
 
+    private static final String KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
+            "launcher.reconfigurable_widget_education_tip_seen";
     private static final Rect sTmpRect = new Rect();
 
     private static final int HANDLE_COUNT = 4;
@@ -238,6 +244,15 @@
                             mWidgetView.getAppWidgetId(),
                             Launcher.REQUEST_RECONFIGURE_APPWIDGET);
             });
+            if (!hasSeenReconfigurableWidgetEducationTip()) {
+                post(() -> {
+                    if (showReconfigurableWidgetEducationTip() != null) {
+                        mLauncher.getSharedPrefs().edit()
+                                .putBoolean(KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN,
+                                        true).apply();
+                    }
+                });
+            }
         }
 
         // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
@@ -679,4 +694,25 @@
                 || keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END
                 || keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
     }
+
+    @Nullable private ArrowTipView showReconfigurableWidgetEducationTip() {
+        Rect rect = new Rect();
+        if (!mReconfigureButton.getGlobalVisibleRect(rect)) {
+            return null;
+        }
+        @Px int tipMargin = mLauncher.getResources()
+                .getDimensionPixelSize(R.dimen.widget_reconfigure_tip_top_margin);
+        return new ArrowTipView(mLauncher, /* isPointingUp= */ true)
+                .showAroundRect(
+                        getContext().getString(R.string.reconfigurable_widget_education_tip),
+                        /* arrowXCoord= */ rect.left + mReconfigureButton.getWidth() / 2,
+                        /* rect= */ rect,
+                        /* margin= */ tipMargin);
+    }
+
+    private boolean hasSeenReconfigurableWidgetEducationTip() {
+        return mLauncher.getSharedPrefs()
+                .getBoolean(KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN, false)
+                || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+    }
 }
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index 07d3776..e449a4b 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -17,8 +17,10 @@
 package com.android.launcher3.views;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.CornerPathEffect;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
 import android.os.Handler;
 import android.util.Log;
@@ -53,9 +55,10 @@
 
     protected final BaseDraggingActivity mActivity;
     private final Handler mHandler = new Handler();
-    private final boolean mIsPointingUp;
     private final int mArrowWidth;
+    private boolean mIsPointingUp;
     private Runnable mOnClosed;
+    private View mArrowView;
 
     public ArrowTipView(Context context) {
         this(context, false);
@@ -73,6 +76,9 @@
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             close(true);
+            if (mActivity.getDragLayer().isEventOverView(this, ev)) {
+                return true;
+            }
         }
         return false;
     }
@@ -106,24 +112,8 @@
         inflate(context, R.layout.arrow_toast, this);
         setOrientation(LinearLayout.VERTICAL);
 
-        View arrowView = findViewById(R.id.arrow);
-        ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
-        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
-                arrowLp.width, arrowLp.height, mIsPointingUp));
-        Paint arrowPaint = arrowDrawable.getPaint();
-        arrowPaint.setColor(ContextCompat.getColor(getContext(), R.color.arrow_tip_view_bg));
-        // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
-        arrowPaint.setPathEffect(new CornerPathEffect(
-                context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
-        arrowView.setBackground(arrowDrawable);
-        if (mIsPointingUp) {
-            removeView(arrowView);
-            addView(arrowView, 0);
-        }
-
-        mIsOpen = true;
-
-        mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
+        mArrowView = findViewById(R.id.arrow);
+        updateArrowTipInView();
     }
 
     /**
@@ -136,10 +126,10 @@
     /**
      * Show the ArrowTipView (tooltip) center, start, or end aligned.
      *
-     * @param text             The text to be shown in the tooltip.
-     * @param gravity          The gravity aligns the tooltip center, start, or end.
+     * @param text The text to be shown in the tooltip.
+     * @param gravity The gravity aligns the tooltip center, start, or end.
      * @param arrowMarginStart The margin from start to place arrow (ignored if center)
-     * @param top              The Y coordinate of the bottom of tooltip.
+     * @param top The Y coordinate of the bottom of tooltip.
      * @return The tooltip.
      */
     public ArrowTipView show(String text, int gravity, int arrowMarginStart, int top) {
@@ -149,8 +139,7 @@
 
         DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
         params.gravity = gravity;
-        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) findViewById(
-                R.id.arrow).getLayoutParams();
+        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mArrowView.getLayoutParams();
         lp.gravity = gravity;
 
         if (parent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
@@ -166,6 +155,9 @@
         params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left;
         params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right;
         post(() -> setY(top - (mIsPointingUp ? 0 : getHeight())));
+
+        mIsOpen = true;
+        mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
         setAlpha(0);
         animate()
                 .alpha(1f)
@@ -178,18 +170,61 @@
     }
 
     /**
-     * Show the ArrowTipView (tooltip) custom aligned.
+     * Show the ArrowTipView (tooltip) custom aligned. The tooltip is vertically flipped if it
+     * cannot fit on screen in the requested orientation.
      *
-     * @param text        The text to be shown in the tooltip.
-     * @param arrowXCoord The X coordinate for the arrow on the tip. The arrow is usually in the
-     *                    center of ArrowTipView unless the ArrowTipView goes beyond screen margin.
-     * @param yCoord      The Y coordinate of the bottom of the tooltip.
-     * @return The tool tip view.
+     * @param text The text to be shown in the tooltip.
+     * @param arrowXCoord The X coordinate for the arrow on the tooltip. The arrow is usually in the
+     *                    center of tooltip unless the tooltip goes beyond screen margin.
+     * @param yCoord The Y coordinate of the pointed tip end of the tooltip.
+     * @return The tool tip view. {@code null} if the tip can not be shown.
      */
-    @Nullable
-    public ArrowTipView showAtLocation(String text, int arrowXCoord, int yCoord) {
+    @Nullable public ArrowTipView showAtLocation(String text, @Px int arrowXCoord, @Px int yCoord) {
+        return showAtLocation(
+                text,
+                arrowXCoord,
+                /* yCoordDownPointingTip= */ yCoord,
+                /* yCoordUpPointingTip= */ yCoord);
+    }
+
+    /**
+     * Show the ArrowTipView (tooltip) custom aligned. The tooltip is vertically flipped if it
+     * cannot fit on screen in the requested orientation.
+     *
+     * @param text The text to be shown in the tooltip.
+     * @param arrowXCoord The X coordinate for the arrow on the tooltip. The arrow is usually in the
+     *                    center of tooltip unless the tooltip goes beyond screen margin.
+     * @param rect The coordinates of the view which requests the tooltip to be shown.
+     * @param margin The margin between {@param rect} and the tooltip.
+     * @return The tool tip view. {@code null} if the tip can not be shown.
+     */
+    @Nullable public ArrowTipView showAroundRect(
+            String text, @Px int arrowXCoord, Rect rect, @Px int margin) {
+        return showAtLocation(
+                text,
+                arrowXCoord,
+                /* yCoordDownPointingTip= */ rect.top - margin,
+                /* yCoordUpPointingTip= */ rect.bottom + margin);
+    }
+
+    /**
+     * Show the ArrowTipView (tooltip) custom aligned. The tooltip is vertically flipped if it
+     * cannot fit on screen in the requested orientation.
+     *
+     * @param text The text to be shown in the tooltip.
+     * @param arrowXCoord The X coordinate for the arrow on the tooltip. The arrow is usually in the
+     *                    center of tooltip unless the tooltip goes beyond screen margin.
+     * @param yCoordDownPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+     *                              tooltip is placed pointing downwards.
+     * @param yCoordUpPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+     *                            tooltip is placed pointing upwards.
+     * @return The tool tip view. {@code null} if the tip can not be shown.
+     */
+    @Nullable private ArrowTipView showAtLocation(String text, @Px int arrowXCoord,
+            @Px int yCoordDownPointingTip, @Px int yCoordUpPointingTip) {
         ViewGroup parent = mActivity.getDragLayer();
         @Px int parentViewWidth = parent.getWidth();
+        @Px int parentViewHeight = parent.getHeight();
         @Px int maxTextViewWidth = getContext().getResources()
                 .getDimensionPixelSize(R.dimen.widget_picker_education_tip_max_width);
         @Px int minViewMargin = getContext().getResources()
@@ -206,22 +241,45 @@
         requestLayout();
 
         post(() -> {
+            // Adjust the tooltip horizontally.
             float halfWidth = getWidth() / 2f;
             float xCoord;
             if (arrowXCoord - halfWidth < minViewMargin) {
+                // If the tooltip is estimated to go beyond the left margin, place its start just at
+                // the left margin.
                 xCoord = minViewMargin;
             } else if (arrowXCoord + halfWidth > parentViewWidth - minViewMargin) {
+                // If the tooltip is estimated to go beyond the right margin, place it such that its
+                // end is just at the right margin.
                 xCoord = parentViewWidth - minViewMargin - getWidth();
             } else {
+                // Place the tooltip such that its center is at arrowXCoord.
                 xCoord = arrowXCoord - halfWidth;
             }
             setX(xCoord);
-            setY(yCoord - getHeight());
-            View arrowView = findViewById(R.id.arrow);
-            arrowView.setX(arrowXCoord - xCoord - arrowView.getWidth() / 2f);
+
+            // Adjust the tooltip vertically.
+            @Px int viewHeight = getHeight();
+            if (mIsPointingUp
+                    ? (yCoordUpPointingTip + viewHeight > parentViewHeight)
+                    : (yCoordDownPointingTip - viewHeight < 0)) {
+                // Flip the view if it exceeds the vertical bounds of screen.
+                mIsPointingUp = !mIsPointingUp;
+                updateArrowTipInView();
+            }
+            // Place the tooltip such that its top is at yCoordUpPointingTip if arrow is displayed
+            // pointing upwards, otherwise place it such that its bottom is at
+            // yCoordDownPointingTip.
+            setY(mIsPointingUp ? yCoordUpPointingTip : yCoordDownPointingTip - viewHeight);
+
+            // Adjust the arrow's relative position on tooltip to make sure the actual position of
+            // arrow's pointed tip is always at arrowXCoord.
+            mArrowView.setX(arrowXCoord - xCoord - mArrowView.getWidth() / 2f);
             requestLayout();
         });
 
+        mIsOpen = true;
+        mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
         setAlpha(0);
         animate()
                 .alpha(1f)
@@ -233,6 +291,27 @@
         return this;
     }
 
+    private void updateArrowTipInView() {
+        ViewGroup.LayoutParams arrowLp = mArrowView.getLayoutParams();
+        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+                arrowLp.width, arrowLp.height, mIsPointingUp));
+        Paint arrowPaint = arrowDrawable.getPaint();
+        @Px int arrowTipRadius = getContext().getResources()
+                .getDimensionPixelSize(R.dimen.arrow_toast_corner_radius);
+        arrowPaint.setColor(ContextCompat.getColor(getContext(), R.color.arrow_tip_view_bg));
+        arrowPaint.setPathEffect(new CornerPathEffect(arrowTipRadius));
+        mArrowView.setBackground(arrowDrawable);
+        // Add negative margin so that the rounded corners on base of arrow are not visible.
+        removeView(mArrowView);
+        if (mIsPointingUp) {
+            addView(mArrowView, 0);
+            ((ViewGroup.MarginLayoutParams) arrowLp).setMargins(0, 0, 0, -1 * arrowTipRadius);
+        } else {
+            addView(mArrowView, 1);
+            ((ViewGroup.MarginLayoutParams) arrowLp).setMargins(0, -1 * arrowTipRadius, 0, 0);
+        }
+    }
+
     /**
      * Register a callback fired when toast is hidden
      */
@@ -240,4 +319,10 @@
         mOnClosed = runnable;
         return this;
     }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        close(/* animate= */ false);
+    }
 }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index edd42b4..3bf993e 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -207,16 +207,18 @@
         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]);
+        ArrowTipView arrowTipView =
+                new ArrowTipView(mActivityContext,  /* isPointingUp= */ false).showAtLocation(
+                        getContext().getString(R.string.long_press_widget_to_add),
+                        /* arrowXCoord= */coords[0] + view.getWidth() / 2,
+                        /* yCoord= */coords[1]);
+        if (arrowTipView != null) {
+            mActivityContext.getSharedPrefs().edit()
+                    .putBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, true).apply();
+        }
+        return arrowTipView;
     }
 
     /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */