Merge "Only draw double shadows of BubbleTextView if both shadow color's alpha value is non-zero." into ub-launcher3-dorval-polish
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 2bbd76d..e6f98a4 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -63,8 +63,8 @@
         <com.android.launcher3.pageindicators.PageIndicatorCaretLandscape
             android:id="@+id/page_indicator"
             android:theme="@style/HomeScreenElementTheme"
-            android:layout_width="@dimen/dynamic_grid_page_indicator_size"
-            android:layout_height="@dimen/dynamic_grid_page_indicator_size"
+            android:layout_width="@dimen/dynamic_grid_min_page_indicator_size"
+            android:layout_height="@dimen/dynamic_grid_min_page_indicator_size"
             android:layout_gravity="bottom|left"/>
 
         <include layout="@layout/widgets_view"
diff --git a/res/layout/horizontal_divider.xml b/res/layout/horizontal_divider.xml
deleted file mode 100644
index 167f8f5..0000000
--- a/res/layout/horizontal_divider.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<View xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/popup_item_divider_height"
-    android:background="?attr/popupColorTertiary" />
\ No newline at end of file
diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml
index 14ff2bd..92f52d6 100644
--- a/res/layout/page_indicator.xml
+++ b/res/layout/page_indicator.xml
@@ -18,11 +18,11 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:theme="@style/HomeScreenElementTheme"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/dynamic_grid_page_indicator_size">
+    android:layout_height="@dimen/dynamic_grid_min_page_indicator_size">
         <ImageView
             android:id="@+id/all_apps_handle"
             android:layout_width="48dp"
-            android:layout_height="match_parent"
+            android:layout_height="@dimen/dynamic_grid_min_page_indicator_size"
             android:layout_gravity="top|center"
             android:scaleType="centerInside"/>
 </com.android.launcher3.pageindicators.PageIndicatorLineCaret>
diff --git a/res/layout/popup_container.xml b/res/layout/popup_container.xml
index e9cfe24..67db4a5 100644
--- a/res/layout/popup_container.xml
+++ b/res/layout/popup_container.xml
@@ -19,8 +19,8 @@
     android:id="@+id/deep_shortcuts_container"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
+    android:paddingTop="4dp"
+    android:paddingBottom="4dp"
     android:clipToPadding="false"
     android:clipChildren="false"
     android:elevation="@dimen/deep_shortcuts_elevation"
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 0db0e61..1d36f75 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -26,7 +26,7 @@
 
     <!-- Dynamic grid -->
     <dimen name="dynamic_grid_overview_bar_item_width">120dp</dimen>
-    <dimen name="dynamic_grid_page_indicator_size">24dp</dimen>
+    <dimen name="dynamic_grid_min_page_indicator_size">24dp</dimen>
     <dimen name="folder_preview_padding">5dp</dimen>
 
     <!-- Hotseat -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 980b714..21abd3c 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -17,7 +17,7 @@
 <resources>
 <!-- Dynamic Grid -->
     <dimen name="dynamic_grid_edge_margin">16dp</dimen>
-    <dimen name="dynamic_grid_page_indicator_size">32dp</dimen>
+    <dimen name="dynamic_grid_min_page_indicator_size">32dp</dimen>
     <dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
     <dimen name="dynamic_grid_page_indicator_gutter_width">50dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index eef6510..7520be2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -80,7 +80,7 @@
     public final int workspaceSpringLoadedBottomSpace;
 
     // Page indicator
-    private final int pageIndicatorSizePx;
+    private int pageIndicatorSizePx;
     private final int pageIndicatorLandGutterPx;
     private final int pageIndicatorLandWorkspaceOffsetPx;
 
@@ -172,7 +172,8 @@
         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         desiredWorkspaceLeftRightMarginPx = edgeMarginPx;
-        pageIndicatorSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_size);
+        pageIndicatorSizePx = res.getDimensionPixelSize(
+                R.dimen.dynamic_grid_min_page_indicator_size);
         pageIndicatorLandGutterPx = res.getDimensionPixelSize(
                 R.dimen.dynamic_grid_page_indicator_gutter_width);
         pageIndicatorLandWorkspaceOffsetPx =
@@ -228,8 +229,23 @@
             availableHeightPx = maxSize.y;
         }
 
-        // Calculate the remaining vars
+        // Calculate all of the remaining variables.
         updateAvailableDimensions(dm, res);
+
+        // Now that we have all of the variables calculated, we can tune certain sizes.
+        if (!isVerticalBarLayout()) {
+            // We increase the page indicator size when there is extra space.
+            // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
+            // in portrait mode closer together by increasing the page indicator size.
+            int newPageIndicatorSizePx = getCellSize().y - iconSizePx - iconTextSizePx
+                    - iconDrawablePaddingOriginalPx;
+            if (newPageIndicatorSizePx > pageIndicatorSizePx) {
+                pageIndicatorSizePx = newPageIndicatorSizePx;
+                // Recalculate the available dimensions using the new page indicator size.
+                updateAvailableDimensions(dm, res);
+            }
+        }
+
         computeAllAppsButtonSize(context);
 
         // This is done last, after iconSizePx is calculated above.
@@ -484,8 +500,8 @@
             return new Rect(mInsets.left,
                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
                     mInsets.left + availableWidthPx,
-                    mInsets.top + availableHeightPx - hotseatBarHeightPx - pageIndicatorSizePx -
-                            edgeMarginPx);
+                    mInsets.top + availableHeightPx - hotseatBarHeightPx
+                            - pageIndicatorSizePx - edgeMarginPx);
         }
     }
 
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index a23a674..3bcd7af 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -68,7 +68,7 @@
     private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
 
     // Empty class name is used for storing package default entry.
-    private static final String EMPTY_CLASS_NAME = ".";
+    public static final String EMPTY_CLASS_NAME = ".";
 
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_IGNORE_CACHE = false;
@@ -120,7 +120,8 @@
     }
 
     private Drawable getFullResDefaultActivityIcon() {
-        return getFullResIcon(Resources.getSystem(), android.R.drawable.sym_def_app_icon);
+        return getFullResIcon(Resources.getSystem(), Utilities.isAtLeastO() ?
+                android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon);
     }
 
     private Drawable getFullResIcon(Resources resources, int iconId) {
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 11c5309..c5be096 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -137,7 +137,18 @@
     }
 
     public ComponentName getTargetComponent() {
-        return getIntent() == null ? null : getIntent().getComponent();
+        Intent intent = getIntent();
+        if (intent == null) {
+            return null;
+        }
+        ComponentName cn = intent.getComponent();
+        if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT && cn == null) {
+            // Legacy shortcuts may not have a componentName but just a packageName. In that case
+            // create a dummy componentName instead of adding additional check everywhere.
+            String pkg = intent.getPackage();
+            return pkg == null ? null : new ComponentName(pkg, IconCache.EMPTY_CLASS_NAME);
+        }
+        return cn;
     }
 
     public void writeToValues(ContentWriter writer) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 4fb0b86..ccef4f2 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -410,22 +410,19 @@
 
         @Override
         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-            if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING
-                    || (dx == 0 && dy == 0)) {
+            if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING || (dx == 0 && dy == 0)) {
                 if (mSpringAnimationHandler.isRunning()){
                     mSpringAnimationHandler.skipToEnd();
                 }
                 return;
             }
 
-            int first = mLayoutManager.findFirstVisibleItemPosition();
-            int last = mLayoutManager.findLastVisibleItemPosition();
-
-            // We only show the spring animation when at the top or bottom, so we wait until the
-            // first or last row is visible to ensure that all animations run in sync.
-            boolean scrollUp = dy < 0;
-            if ((first == 0 && scrollUp) || (last == mAdapter.getItemCount() - 1 && dy > 0)) {
-                mSpringAnimationHandler.animateToFinalPosition(0, scrollUp ? 1 : -1);
+            // We only start the spring animation when we fling and hit the top/bottom, to ensure
+            // that all of the animations start at the same time.
+            if (dy < 0 && !mAppsRecyclerView.canScrollVertically(-1)) {
+                mSpringAnimationHandler.animateToFinalPosition(0, 1);
+            } else if (dy > 0 && !mAppsRecyclerView.canScrollVertically(1)) {
+                mSpringAnimationHandler.animateToFinalPosition(0, -1);
             }
         }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index ff8de88..fb785fb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -96,8 +96,7 @@
         mOverScrollHelper = new OverScrollHelper();
         mPullDetector = new VerticalPullDetector(getContext());
         mPullDetector.setListener(mOverScrollHelper);
-        mPullDetector.setDetectableScrollConditions(VerticalPullDetector.DIRECTION_UP
-                | VerticalPullDetector.DIRECTION_DOWN, true);
+        mPullDetector.setDetectableScrollConditions(VerticalPullDetector.DIRECTION_BOTH, true);
     }
 
     public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
@@ -487,28 +486,53 @@
 
         private boolean mIsInOverScroll;
 
+        // We use this value to calculate the actual amount the user has overscrolled.
+        private float mFirstDisplacement = 0;
+
+        private boolean mAlreadyScrollingUp;
+        private int mFirstScrollYOnScrollUp;
+
         @Override
         public void onDragStart(boolean start) {
         }
 
         @Override
         public boolean onDrag(float displacement, float velocity) {
-            // We are in overscroll iff we are trying to drag further down when we're already at
-            // the bottom of All Apps.
-            mIsInOverScroll = !canScrollVertically(1) && displacement < 0
-                    && !mScrollbar.isDraggingThumb();
+            boolean isScrollingUp = displacement > 0;
+            if (isScrollingUp) {
+                if (!mAlreadyScrollingUp) {
+                    mFirstScrollYOnScrollUp = getCurrentScrollY();
+                    mAlreadyScrollingUp = true;
+                }
+            } else {
+                mAlreadyScrollingUp = false;
+            }
+
+            // Only enter overscroll if the user is interacting with the RecyclerView directly
+            // and if one of the following criteria are met:
+            // - User scrolls down when they're already at the bottom.
+            // - User starts scrolling up, hits the top, and continues scrolling up.
+            mIsInOverScroll = !mScrollbar.isDraggingThumb() &&
+                    ((!canScrollVertically(1) && displacement < 0) ||
+                    (!canScrollVertically(-1) && isScrollingUp && mFirstScrollYOnScrollUp != 0));
 
             if (mIsInOverScroll) {
-                displacement = getDampedOverScroll(displacement);
-                setContentTranslationY(displacement);
+                if (Float.compare(mFirstDisplacement, 0) == 0) {
+                    // Because users can scroll before entering overscroll, we need to
+                    // subtract the amount where the user was not in overscroll.
+                    mFirstDisplacement = displacement;
+                }
+                float overscrollY = displacement - mFirstDisplacement;
+                setContentTranslationY(getDampedOverScroll(overscrollY));
             }
+
             return mIsInOverScroll;
         }
 
         @Override
         public void onDragEnd(float velocity, boolean fling) {
             float y = getContentTranslationY();
-            if (mIsInOverScroll && Float.compare(y, 0) != 0) {
+            if (Float.compare(y, 0) != 0) {
                 if (FeatureFlags.LAUNCHER3_PHYSICS) {
                     // We calculate our own velocity to give the springs the desired effect.
                     velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
@@ -523,6 +547,9 @@
                         .start();
             }
             mIsInOverScroll = false;
+            mFirstDisplacement = 0;
+            mFirstScrollYOnScrollUp = 0;
+            mAlreadyScrollingUp = false;
         }
 
         public boolean isInOverScroll() {
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index eb6a6d5..61490ee 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -306,6 +306,7 @@
     // It is the callers responsibility to save and restore the canvas layers.
     void clipCanvasHardware(Canvas canvas) {
         mPaint.setColor(Color.BLACK);
+        mPaint.setStyle(Paint.Style.FILL);
         mPaint.setXfermode(mClipPorterDuffXfermode);
 
         float radius = getScaledRadius();
@@ -336,6 +337,7 @@
         }
 
         mDrawingDelegate = null;
+        isClipping = true;
         invalidate();
     }
 
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index b83c9b9..2455eab 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -206,7 +206,10 @@
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         ((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE);
-                        ((ViewGroup) getParent()).removeView(NotificationFooterLayout.this);
+                        // Keep view around because gutter is aligned to it, but remove height to
+                        // both hide the view and keep calculations correct for last dismissal.
+                        getLayoutParams().height = 0;
+                        requestLayout();
                     }
                 });
                 collapseFooter.start();
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 0cd5a4c..11f6aa0 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.notification;
 
 import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.Rect;
@@ -28,7 +30,9 @@
 import android.widget.TextView;
 
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
@@ -89,6 +93,8 @@
     }
 
     public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
+        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+
         Rect startRect = new Rect(mPillRect);
         Rect endRect = new Rect(mPillRect);
         if (shouldRemoveFromTop) {
@@ -96,8 +102,18 @@
         } else {
             endRect.bottom -= heightToRemove;
         }
-        return new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
-                startRect, endRect, mRoundedCorners).createRevealAnimator(this, false);
+        anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
+                startRect, endRect, mRoundedCorners).createRevealAnimator(this, false));
+
+        View bottomGutter = findViewById(R.id.gutter_bottom);
+        if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) {
+            Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y,
+                    -heightToRemove);
+            translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f));
+            anim.play(translateGutter);
+        }
+
+        return anim;
     }
 
     public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index c6ae0d2..3de9bad 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -44,7 +44,6 @@
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateDecelerateInterpolator;
-import android.widget.FrameLayout;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
@@ -118,6 +117,7 @@
     private boolean mIsLeftAligned;
     protected boolean mIsAboveIcon;
     private View mArrow;
+    private int mGravity;
 
     protected Animator mOpenCloseAnimator;
     private boolean mDeferContainerRemoval;
@@ -490,9 +490,10 @@
         }
         y -= insets.top;
 
-        if (y < dragLayer.getTop() || y + height > dragLayer.getBottom()) {
+        mGravity = 0;
+        if (y + height > dragLayer.getBottom() - insets.bottom) {
             // The container is opening off the screen, so just center it in the drag layer instead.
-            ((FrameLayout.LayoutParams) getLayoutParams()).gravity = Gravity.CENTER_VERTICAL;
+            mGravity = Gravity.CENTER_VERTICAL;
             // Put the container next to the icon, preferring the right side in ltr (left in rtl).
             int rightSide = leftAlignedX + iconWidth - insets.left;
             int leftSide = rightAlignedX - iconWidth - insets.left;
@@ -518,14 +519,17 @@
 
         if (x < dragLayer.getLeft() || x + width > dragLayer.getRight()) {
             // If we are still off screen, center horizontally too.
-            ((FrameLayout.LayoutParams) getLayoutParams()).gravity |= Gravity.CENTER_HORIZONTAL;
+            mGravity |= Gravity.CENTER_HORIZONTAL;
         }
 
-        int gravity = ((FrameLayout.LayoutParams) getLayoutParams()).gravity;
-        if (!Gravity.isHorizontal(gravity)) {
+        if (Gravity.isHorizontal(mGravity)) {
+            setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
+        } else {
             setX(x);
         }
-        if (!Gravity.isVertical(gravity)) {
+        if (Gravity.isVertical(mGravity)) {
+            setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
+        } else {
             setY(y);
         }
     }
@@ -555,7 +559,7 @@
         }
 
         View arrowView = new View(getContext());
-        if (Gravity.isVertical(((FrameLayout.LayoutParams) getLayoutParams()).gravity)) {
+        if (Gravity.isVertical(mGravity)) {
             // This is only true if there wasn't room for the container next to the icon,
             // so we centered it instead. In that case we don't want to show the arrow.
             arrowView.setVisibility(INVISIBLE);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
index f6fffe0..b4fa04e 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
@@ -24,7 +24,6 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -145,18 +144,8 @@
             if (mSystemShortcutIcons == null) {
                 mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
                         R.layout.system_shortcut_icons, mContent, false);
-
-                View divider = LayoutInflater.from(getContext()).inflate(
-                        R.layout.horizontal_divider, this, false);
-
                 boolean iconsAreBelowShortcuts = mShortcutsLayout.getChildCount() > 0;
-                if (iconsAreBelowShortcuts) {
-                    mContent.addView(divider);
-                    mContent.addView(mSystemShortcutIcons);
-                } else {
-                    mContent.addView(divider, 0);
-                    mContent.addView(mSystemShortcutIcons, 0);
-                }
+                mContent.addView(mSystemShortcutIcons, iconsAreBelowShortcuts ? -1 : 0);
             }
             mSystemShortcutIcons.addView(shortcutView, index);
         } else {