Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ad41a561 authored by Pierre Barbier de Reuille's avatar Pierre Barbier de Reuille
Browse files

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
parent ce271c10
Loading
Loading
Loading
Loading
+21 −6
Original line number Diff line number Diff line
@@ -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
+1 −28
Original line number Diff line number Diff line
@@ -17,12 +17,8 @@ package com.android.launcher3.dragndrop;

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 @@ public final class AppWidgetHostViewDrawable extends Drawable {

    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);
        }
        canvas.restoreToCount(saveCount);
    }

+16 −5
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;

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 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView

        // 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 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView
        dragHelper.setRemoteViewsPreview(v.getPreview());
        dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());

        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());
        } 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;
    }
+1 −0
Original line number Diff line number Diff line
@@ -115,6 +115,7 @@ public class PendingItemDragHelper extends DragPreviewProvider {
            }
            if (mAppWidgetHostViewPreview != null) {
                preview = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
                previewSizeBeforeScale[0] = mAppWidgetHostViewPreview.getMeasuredWidth();
                launcher.getDragController()
                        .addDragListener(new AppWidgetHostViewDragListener(launcher));
            }
+49 −24
Original line number Diff line number Diff line
@@ -28,9 +28,11 @@ import android.util.Log;
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.CheckLongPressHelper;
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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
    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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
    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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
            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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
            mActiveRequest = null;
        }
        mPreview = null;
        if (mAppWidgetHostViewPreview != null) {
            mWidgetImageContainer.removeView(mAppWidgetHostViewPreview);
        }
        mAppWidgetHostViewPreview = null;
    }

@@ -215,6 +225,13 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
            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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
            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)));
            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) {
                mWidgetImage.setAlpha(0f);
                ViewPropertyAnimator anim = mWidgetImage.animate();
            mWidgetImageContainer.setAlpha(0f);
            ViewPropertyAnimator anim = mWidgetImageContainer.animate();
            anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
        } else {
                mWidgetImage.setAlpha(1f);
            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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
            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) {
Loading