Stop trying to draw a view not attached to the view tree

The behavior of the framework when we try to do so is undefined. In our
case, it almost work, but no clipping is applied, which is a problem for
Android S (before that, widget couldn't use clipping in the first
place).

Instead of drawing the view through a drawable, this really add the view
and adds also a badge ImageView for badges instead of drawing them
indirectly.

Note that, temporarily, we have to re-allow drawing the view after it
has been attached, but the underlying framework bug being fixed, this
should be fine (I tested it and it really seems to be).

Bug: 183609936
Test: Using hand designed app (see bug)
Change-Id: I929ef8fc81c98c49406f2d940cd5efc28319886d
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 30bd8b1..0f6fc6c 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -17,16 +17,31 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
 
-    <!-- The image of the widget. This view does not support padding. Any placement adjustment
-         should be done using margins. Width & height are set at runtime after scaling the preview
-         image. -->
-    <com.android.launcher3.widget.WidgetImageView
-        android:id="@+id/widget_preview"
+    <com.android.launcher3.widget.WidgetCellPreview
+        android:id="@+id/widget_preview_container"
         android:layout_width="0dp"
         android:layout_height="0dp"
         android:layout_weight="1"
         android:importantForAccessibility="no"
-        android:layout_marginVertical="8dp" />
+        android:layout_marginVertical="8dp">
+        <!-- The image of the widget. This view does not support padding. Any placement adjustment
+             should be done using margins. Width & height are set at runtime after scaling the
+             preview image. -->
+        <com.android.launcher3.widget.WidgetImageView
+            android:id="@+id/widget_preview"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:importantForAccessibility="no"
+            android:layout_gravity="fill"/>
+
+        <ImageView
+            android:id="@+id/widget_badge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:importantForAccessibility="no"
+            android:layout_gravity="end|bottom"
+            android:layout_margin="@dimen/profile_badge_margin"/>
+    </com.android.launcher3.widget.WidgetCellPreview>
 
     <!-- The name of the widget. -->
     <TextView
diff --git a/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
index 7b32bbf..2135f5d 100644
--- a/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
+++ b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
@@ -17,12 +17,8 @@
 
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
-import android.graphics.Outline;
 import android.graphics.Paint;
-import android.graphics.Path;
 import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -36,38 +32,15 @@
 
     private final LauncherAppWidgetHostView mAppWidgetHostView;
     private Paint mPaint = new Paint();
-    private final Path mClipPath;
-    private final boolean mWasAttached;
 
     public AppWidgetHostViewDrawable(LauncherAppWidgetHostView appWidgetHostView) {
         mAppWidgetHostView = appWidgetHostView;
-        mWasAttached = appWidgetHostView.isAttachedToWindow();
-        Path clipPath = null;
-        if (appWidgetHostView.getClipToOutline()) {
-            Outline outline = new Outline();
-            mAppWidgetHostView.getOutlineProvider().getOutline(mAppWidgetHostView, outline);
-            Rect rect = new Rect();
-            if (outline.getRect(rect)) {
-                float radius = outline.getRadius();
-                clipPath = new Path();
-                clipPath.addRoundRect(new RectF(rect), radius, radius, Path.Direction.CCW);
-            }
-        }
-        mClipPath = clipPath;
     }
 
     @Override
     public void draw(Canvas canvas) {
         int saveCount = canvas.saveLayer(0, 0, getIntrinsicWidth(), getIntrinsicHeight(), mPaint);
-        if (mClipPath != null) {
-            canvas.clipPath(mClipPath);
-        }
-        // If the view was never attached, or is current attached, then draw. Otherwise do not try
-        // to draw, or we might trigger bugs with items that get drawn while requiring the view to
-        // be attached.
-        if (!mWasAttached || mAppWidgetHostView.isAttachedToWindow()) {
-            mAppWidgetHostView.draw(canvas);
-        }
+        mAppWidgetHostView.draw(canvas);
         canvas.restoreToCount(saveCount);
     }
 
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index fc63af0..95b887c 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -108,7 +109,7 @@
 
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
-        if (image.getDrawable() == null) {
+        if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) {
             return false;
         }
 
@@ -116,11 +117,21 @@
         dragHelper.setRemoteViewsPreview(v.getPreview());
         dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
 
-        int[] loc = new int[2];
-        getPopupContainer().getLocationInDragLayer(image, loc);
+        if (image.getDrawable() != null) {
+            int[] loc = new int[2];
+            getPopupContainer().getLocationInDragLayer(image, loc);
 
-        dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
-                image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
+            dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
+                    image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
+        } else {
+            View preview = v.getAppWidgetHostViewPreview();
+            int[] loc = new int[2];
+            getPopupContainer().getLocationInDragLayer(preview, loc);
+
+            Rect r = new Rect(0, 0, preview.getWidth(), preview.getHeight());
+            dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(),
+                    new Point(loc[0], loc[1]), this, new DragOptions());
+        }
         close(true);
         return true;
     }
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 3e61e56..e78d517 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -115,6 +115,7 @@
             }
             if (mAppWidgetHostViewPreview != null) {
                 preview = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
+                previewSizeBeforeScale[0] = mAppWidgetHostViewPreview.getMeasuredWidth();
                 launcher.getDragController()
                         .addDragListener(new AppWidgetHostViewDragListener(launcher));
             }
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 40b256b..5e7c961 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -28,9 +28,11 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RemoteViews;
 import android.widget.TextView;
@@ -42,7 +44,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.FastBitmapDrawable;
@@ -77,7 +78,9 @@
     private int mCellSize;
     private float mPreviewScale = 1f;
 
+    private FrameLayout mWidgetImageContainer;
     private WidgetImageView mWidgetImage;
+    private ImageView mWidgetBadge;
     private TextView mWidgetName;
     private TextView mWidgetDims;
     private TextView mWidgetDescription;
@@ -133,7 +136,9 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
+        mWidgetImageContainer = findViewById(R.id.widget_preview_container);
         mWidgetImage = findViewById(R.id.widget_preview);
+        mWidgetBadge = findViewById(R.id.widget_badge);
         mWidgetName = findViewById(R.id.widget_name);
         mWidgetDims = findViewById(R.id.widget_dims);
         mWidgetDescription = findViewById(R.id.widget_description);
@@ -155,7 +160,9 @@
             Log.d(TAG, "reset called on:" + mWidgetName.getText());
         }
         mWidgetImage.animate().cancel();
-        mWidgetImage.setDrawable(null, null);
+        mWidgetImage.setDrawable(null);
+        mWidgetImage.setVisibility(View.VISIBLE);
+        mWidgetBadge.setImageDrawable(null);
         mWidgetName.setText(null);
         mWidgetDims.setText(null);
         mWidgetDescription.setText(null);
@@ -167,6 +174,9 @@
             mActiveRequest = null;
         }
         mPreview = null;
+        if (mAppWidgetHostViewPreview != null) {
+            mWidgetImageContainer.removeView(mAppWidgetHostViewPreview);
+        }
         mAppWidgetHostViewPreview = null;
     }
 
@@ -215,6 +225,13 @@
             mAppWidgetHostViewPreview.setPadding(/* left= */ 0, /* top= */0, /* right= */
                     0, /* bottom= */ 0);
             mAppWidgetHostViewPreview.updateAppWidget(/* remoteViews= */ null);
+            // Gravity 77 = "fill"
+            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT, /* gravity= */ 77);
+            mAppWidgetHostViewPreview.setLayoutParams(params);
+            mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0);
+            mWidgetImage.setVisibility(View.GONE);
         }
     }
 
@@ -258,21 +275,36 @@
             return;
         }
         if (drawable != null) {
-            LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
-            layoutParams.width = (int) (drawable.getIntrinsicWidth() * mPreviewScale);
-            layoutParams.height = (int) (drawable.getIntrinsicHeight() * mPreviewScale);
-            mWidgetImage.setLayoutParams(layoutParams);
-
-            mWidgetImage.setDrawable(drawable, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
-                    BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
-            if (mAnimatePreview) {
-                mWidgetImage.setAlpha(0f);
-                ViewPropertyAnimator anim = mWidgetImage.animate();
-                anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
-            } else {
-                mWidgetImage.setAlpha(1f);
+            setContainerSize(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+            mWidgetImage.setDrawable(drawable);
+            mWidgetImage.setVisibility(View.VISIBLE);
+            if (mAppWidgetHostViewPreview != null) {
+                removeView(mAppWidgetHostViewPreview);
+                mAppWidgetHostViewPreview = null;
             }
         }
+        Drawable badge = mWidgetPreviewLoader.getBadgeForUser(mItem.user,
+                BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx));
+        if (badge == null) {
+            mWidgetBadge.setVisibility(View.GONE);
+        } else {
+            mWidgetBadge.setVisibility(View.VISIBLE);
+            mWidgetBadge.setImageDrawable(badge);
+        }
+        if (mAnimatePreview) {
+            mWidgetImageContainer.setAlpha(0f);
+            ViewPropertyAnimator anim = mWidgetImageContainer.animate();
+            anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+        } else {
+            mWidgetImageContainer.setAlpha(1f);
+        }
+    }
+
+    private void setContainerSize(int width, int height) {
+        LayoutParams layoutParams = (LayoutParams) mWidgetImageContainer.getLayoutParams();
+        layoutParams.width = (int) (width * mPreviewScale);
+        layoutParams.height = (int) (height * mPreviewScale);
+        mWidgetImageContainer.setLayoutParams(layoutParams);
     }
 
     public void ensurePreview() {
@@ -290,15 +322,8 @@
             int viewWidth = dp.cellWidthPx * mItem.spanX;
             int viewHeight = dp.cellHeightPx * mItem.spanY;
 
-            mAppWidgetHostViewPreview.measure(
-                    MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
-
-            viewWidth = mAppWidgetHostViewPreview.getMeasuredWidth();
-            viewHeight = mAppWidgetHostViewPreview.getMeasuredHeight();
-            mAppWidgetHostViewPreview.layout(0, 0, viewWidth, viewHeight);
-            Drawable drawable = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
-            applyPreview(drawable);
+            setContainerSize(viewWidth, viewHeight);
+            applyPreview((Drawable) null);
             return;
         }
         if (mActiveRequest != null) {
diff --git a/src/com/android/launcher3/widget/WidgetCellPreview.java b/src/com/android/launcher3/widget/WidgetCellPreview.java
new file mode 100644
index 0000000..ad3a61a
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetCellPreview.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+/**
+ * View group managing the widget preview: either using a {@link WidgetImageView} or an actual
+ * {@link LauncherAppWidgetHostView}.
+ */
+public class WidgetCellPreview extends FrameLayout {
+    public WidgetCellPreview(Context context) {
+        this(context, null);
+    }
+
+    public WidgetCellPreview(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public WidgetCellPreview(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        super.onInterceptTouchEvent(ev);
+        return true;
+    }
+
+}
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index 39d701c..11f4485 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -25,7 +25,6 @@
 import android.view.View;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 
 /**
  * View that draws a bitmap horizontally centered. If the image width is greater than the view
@@ -37,7 +36,6 @@
     private final int mBadgeMargin;
 
     private Drawable mDrawable;
-    private Drawable mBadge;
 
     public WidgetImageView(Context context) {
         this(context, null);
@@ -54,9 +52,9 @@
                 .getDimensionPixelSize(R.dimen.profile_badge_margin);
     }
 
-    public void setDrawable(Drawable drawable, Drawable badge) {
+    /** Set the drawable to use for this view. */
+    public void setDrawable(Drawable drawable) {
         mDrawable = drawable;
-        mBadge = badge;
         invalidate();
     }
 
@@ -70,11 +68,6 @@
             updateDstRectF();
             mDrawable.setBounds(getBitmapBounds());
             mDrawable.draw(canvas);
-
-            // Only draw the badge if a preview was drawn.
-            if (mBadge != null) {
-                mBadge.draw(canvas);
-            }
         }
     }
 
@@ -105,17 +98,6 @@
             mDstRectF.top = (myHeight - scaledHeight) / 2;
             mDstRectF.bottom = (myHeight + scaledHeight) / 2;
         }
-
-        if (mBadge != null) {
-            Rect bounds = mBadge.getBounds();
-            int left = Utilities.boundToRange(
-                    (int) (mDstRectF.right + mBadgeMargin - bounds.width()),
-                    mBadgeMargin, getWidth() - bounds.width());
-            int top = Utilities.boundToRange(
-                    (int) (mDstRectF.bottom + mBadgeMargin - bounds.height()),
-                    mBadgeMargin, getHeight() - bounds.height());
-            mBadge.setBounds(left, top, bounds.width() + left, bounds.height() + top);
-        }
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index c1d64b1..1524ab3 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
 import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.WidgetImageView;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
 
@@ -170,7 +169,7 @@
                     WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
                             R.layout.widget_cell, tableRow, false);
                     // set up touch.
-                    WidgetImageView preview = widget.findViewById(R.id.widget_preview);
+                    View preview = widget.findViewById(R.id.widget_preview_container);
                     preview.setOnClickListener(mIconClickListener);
                     preview.setOnLongClickListener(mIconLongClickListener);
                     tableRow.addView(widget);