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

Commit 0d96baec authored by Cyrus Boadway's avatar Cyrus Boadway
Browse files

Defer onColorsChanged updates during widget-to-home animation

Color change updates recreate the view, which can interfere with the
widget-to-home return animation, specifically the GhostView's use of the
AppWidgetHostView's render node.

Deferring the application of color changes until the end of the
animation allows the widget to settle and remove the GhostView before a
color change might recreate the widget's view.

Bug: 190818220
Test: manual
Change-Id: I6552e583ebb0e4810077d4e70fe9ecb07fd5d01a
parent d4b713c7
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -152,6 +152,7 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener,
            RectF widgetBackgroundPosition, Size windowSize, float windowCornerRadius,
            boolean appTargetIsTranslucent, int fallbackBackgroundColor) {
        mAppWidgetView = originalView;
        // Deferrals must begin before GhostView is created. See b/190818220
        mAppWidgetView.beginDeferringUpdates();
        mBackgroundPosition = widgetBackgroundPosition;
        mAppTargetIsTranslucent = appTargetIsTranslucent;
@@ -240,6 +241,7 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener,
        ((ViewGroup) dragLayer.getParent()).removeView(this);
        dragLayer.removeView(mListenerView);
        mBackgroundView.finish();
        // Removing GhostView must occur before ending deferrals. See b/190818220
        mAppWidgetView.endDeferringUpdates();
        recycle();
        mLauncher.getViewCache().recycleView(R.layout.floating_widget_view, this);
+36 −9
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;

import java.util.List;
import java.util.Optional;

/**
 * {@inheritDoc}
@@ -116,7 +117,8 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
    private final Object mUpdateLock = new Object();
    private final ViewGroupFocusHelper mDragLayerRelativeCoordinateHelper;
    private long mDeferUpdatesUntilMillis = 0;
    private RemoteViews mMostRecentRemoteViews;
    private RemoteViews mDeferredRemoteViews;
    private Optional<SparseIntArray> mDeferredColorChange = Optional.empty();

    public LauncherAppWidgetHostView(Context context) {
        super(context);
@@ -173,8 +175,11 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
    @Override
    public void updateAppWidget(RemoteViews remoteViews) {
        synchronized (mUpdateLock) {
            mMostRecentRemoteViews = remoteViews;
            if (SystemClock.uptimeMillis() < mDeferUpdatesUntilMillis) return;
            if (isDeferringUpdates()) {
                mDeferredRemoteViews = remoteViews;
                return;
            }
            mDeferredRemoteViews = null;
        }

        super.updateAppWidget(remoteViews);
@@ -210,11 +215,20 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
        return false;
    }

    /**
     * Returns true if the application of {@link RemoteViews} through {@link #updateAppWidget} and
     * colors through {@link #onColorsChanged} are currently being deferred.
     * @see #beginDeferringUpdates()
     */
    private boolean isDeferringUpdates() {
        return SystemClock.uptimeMillis() < mDeferUpdatesUntilMillis;
    }

    /**
     * Begin deferring the application of any {@link RemoteViews} updates made through
     * {@link #updateAppWidget(RemoteViews)} until {@link #endDeferringUpdates()} has been called or
     * the next {@link #updateAppWidget(RemoteViews)} call after {@link #UPDATE_LOCK_TIMEOUT_MILLIS}
     * have elapsed.
     * {@link #updateAppWidget} and color changes through {@link #onColorsChanged} until
     * {@link #endDeferringUpdates()} has been called or the next {@link #updateAppWidget} or
     * {@link #onColorsChanged} call after {@link #UPDATE_LOCK_TIMEOUT_MILLIS} have elapsed.
     */
    public void beginDeferringUpdates() {
        synchronized (mUpdateLock) {
@@ -224,18 +238,23 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView

    /**
     * Stop deferring the application of {@link RemoteViews} updates made through
     * {@link #updateAppWidget(RemoteViews)} and apply the most recently received update.
     * {@link #updateAppWidget} and color changes made through {@link #onColorsChanged} and apply
     * any deferred updates.
     */
    public void endDeferringUpdates() {
        RemoteViews remoteViews;
        Optional<SparseIntArray> deferredColors;
        synchronized (mUpdateLock) {
            mDeferUpdatesUntilMillis = 0;
            remoteViews = mMostRecentRemoteViews;
            mMostRecentRemoteViews = null;
            remoteViews = mDeferredRemoteViews;
            mDeferredRemoteViews = null;
            deferredColors = mDeferredColorChange;
            mDeferredColorChange = Optional.empty();
        }
        if (remoteViews != null) {
            updateAppWidget(remoteViews);
        }
        deferredColors.ifPresent(colors -> onColorsChanged(null /* rectF */, colors));
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -388,6 +407,14 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView

    @Override
    public void onColorsChanged(RectF rectF, SparseIntArray colors) {
        synchronized (mUpdateLock) {
            if (isDeferringUpdates()) {
                mDeferredColorChange = Optional.ofNullable(colors);
                return;
            }
            mDeferredColorChange = Optional.empty();
        }

        // setColorResources will reapply the view, which must happen in the UI thread.
        post(() -> setColorResources(colors));
    }