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

Commit e5dbb75a authored by Pinyao Ting's avatar Pinyao Ting
Browse files

Cache and reuses LauncherAppWidgetHostView when launcher resumes

Currently by design when launcher enters the background, it stops
listening to updates in widgets. This eventually causes the dilemma
for launcher when it resumes, before the update can be returned from
the system process via IPC, launcher could do one of the following
to fill the gap:
1. show a deferred widget view -- a placeholder that renders the shape
   of the widget -- to let the user know widget is being reloaded.
2. show whichever widget view that was previously displayed to the
   user that may now contain stale content.

There is a descrepancy here since in some edge cases we are showing the
former while in most other cases we are showing the later. This CL added
a short-term fix to address the descrepancy and favors the later where
possible.

Bug: 218067434
Test: manual
Change-Id: I6cd2cd704186267227e2ec47f2581843fd526fa0
parent 9074e977
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -274,6 +274,10 @@ public final class FeatureFlags {
            "ENABLE_DISMISS_PREDICTION_UNDO", false,
            "Show an 'Undo' snackbar when users dismiss a predicted hotseat item");

    public static final BooleanFlag ENABLE_CACHED_WIDGET = getDebugFlag(
            "ENABLE_CACHED_WIDGET", true,
            "Show previously cached widgets as opposed to deferred widget where available");

    public static void initialize(Context context) {
        synchronized (sDebugFlags) {
            for (DebugFlag flag : sDebugFlags) {
+55 −6
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.SparseArray;
import android.widget.RemoteViews;
import android.widget.Toast;

import androidx.annotation.Nullable;
@@ -37,6 +38,7 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
@@ -70,13 +72,14 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
    private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
    private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
    private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
    private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
    private final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();

    private final Context mContext;
    private int mFlags = FLAG_STATE_IS_NORMAL;

    private IntConsumer mAppWidgetRemovedCallback = null;


    public LauncherAppWidgetHost(Context context) {
        this(context, null);
    }
@@ -95,6 +98,11 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
        if (mPendingViews.get(appWidgetId) != null) {
            view = mPendingViews.get(appWidgetId);
            mPendingViews.remove(appWidgetId);
        } else if (mDeferredViews.get(appWidgetId) != null) {
            // In case the widget view is deferred, we will simply return the deferred view as
            // opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher
            // already added the former to the workspace.
            view = mDeferredViews.get(appWidgetId);
        } else {
            view = new LauncherAppWidgetHostView(context);
        }
@@ -120,12 +128,25 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
            // widgets upon bind anyway. See issue 14255011 for more context.
        }

        // We go in reverse order and inflate any deferred widget
        // We go in reverse order and inflate any deferred or cached widget
        for (int i = mViews.size() - 1; i >= 0; i--) {
            LauncherAppWidgetHostView view = mViews.valueAt(i);
            if (view instanceof DeferredAppWidgetHostView) {
                view.reInflate();
            }
            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
                final int appWidgetId = mViews.keyAt(i);
                if (view == mDeferredViews.get(appWidgetId)) {
                    // If the widget view was deferred, we'll need to call super.createView here
                    // to make the binder call to system process to fetch cumulative updates to this
                    // widget, as well as setting up this view for future updates.
                    super.createView(view.mLauncher, appWidgetId, view.getAppWidgetInfo());
                    // At this point #onCreateView should have been called, which in turn returned
                    // the deferred view. There's no reason to keep the reference anymore, so we
                    // removed it here.
                    mDeferredViews.remove(appWidgetId);
                }
            }
        }
    }

@@ -221,10 +242,28 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
            return lahv;
        } else if ((mFlags & FLAG_LISTENING) == 0) {
            // Since the launcher hasn't started listening to widget updates, we can't simply call
            // super.createView here because the later will make a binder call to retrieve
            // RemoteViews from system process.
            // TODO: have launcher always listens to widget updates in background so that this
            //  check can be removed altogether.
            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
                    && mCachedRemoteViews.get(appWidgetId) != null) {
                // We've found RemoteViews from cache for this widget, so we will instantiate a
                // widget host view and populate it with the cached RemoteViews.
                final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
                view.setAppWidget(appWidgetId, appWidget);
                view.updateAppWidget(mCachedRemoteViews.get(appWidgetId));
                mDeferredViews.put(appWidgetId, view);
                mViews.put(appWidgetId, view);
                return view;
            } else {
                // When cache misses, a placeholder for the widget will be returned instead.
                DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
                view.setAppWidget(appWidgetId, appWidget);
                mViews.put(appWidgetId, view);
                return view;
            }
        } else {
            try {
                return super.createView(context, appWidgetId, appWidget);
@@ -281,6 +320,16 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
    @Override
    public void clearViews() {
        super.clearViews();
        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
            // First, we clear any previously cached content from existing widgets
            mCachedRemoteViews.clear();
            // Then we proceed to cache the content from the widgets
            for (int i = 0; i < mViews.size(); i++) {
                final int appWidgetId = mViews.keyAt(i);
                final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
                mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
            }
        }
        mViews.clear();
    }

+14 −7
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -85,7 +86,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
    private Runnable mAutoAdvanceRunnable;

    private long mDeferUpdatesUntilMillis = 0;
    private RemoteViews mDeferredRemoteViews;
    RemoteViews mLastRemoteViews;
    private boolean mHasDeferredColorChange = false;
    private @Nullable SparseIntArray mDeferredColorChange = null;

@@ -150,11 +151,18 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
                    TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
            mTrackingWidgetUpdate = false;
        }
        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
            mLastRemoteViews = remoteViews;
            if (isDeferringUpdates()) {
            mDeferredRemoteViews = remoteViews;
                return;
            }
        mDeferredRemoteViews = null;
        } else {
            if (isDeferringUpdates()) {
                mLastRemoteViews = remoteViews;
                return;
            }
            mLastRemoteViews = null;
        }

        super.updateAppWidget(remoteViews);

@@ -218,8 +226,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
        SparseIntArray deferredColors;
        boolean hasDeferredColors;
        mDeferUpdatesUntilMillis = 0;
        remoteViews = mDeferredRemoteViews;
        mDeferredRemoteViews = null;
        remoteViews = mLastRemoteViews;
        deferredColors = mDeferredColorChange;
        hasDeferredColors = mHasDeferredColorChange;
        mDeferredColorChange = null;