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
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
index 22ce942..88b11a0 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -152,6 +152,7 @@
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 @@
((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);
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 70ed02f..ea08a25 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -57,6 +57,7 @@
import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
import java.util.List;
+import java.util.Optional;
/**
* {@inheritDoc}
@@ -116,7 +117,8 @@
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 @@
@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);
@@ -211,10 +216,19 @@
}
/**
+ * 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 @@
/**
* 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 @@
@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));
}