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

Commit fdeb19e0 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Simplifying pending widget inflation path

Instead of adding a PendingView in appWidgetHost, attaching a
listener in PendingView for widget-updates.

This moves the view inflation as the last stage of widget
binding. Eventually we can separate the model update and view
inflation to different threads

Bug: 318539160
Test: atest TaplBinderTests
Flag: None
Change-Id: I124edaa9622fa367302b80969036d128a978396e
parent 3c52d931
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.Looper;
import androidx.annotation.NonNull;

import com.android.launcher3.LauncherAppState;
import com.android.launcher3.util.Executors;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;

@@ -55,7 +56,10 @@ final class QuickstepAppWidgetHost extends AppWidgetHost {

    @Override
    public void onAppWidgetRemoved(int appWidgetId) {
        mAppWidgetRemovedCallback.accept(appWidgetId);
        // Route the call via model thread, in case it comes while a loader-bind is in progress
        Executors.MODEL_EXECUTOR.execute(
                () -> Executors.MAIN_EXECUTOR.execute(
                        () -> mAppWidgetRemovedCallback.accept(appWidgetId)));
    }

    @Override
+39 −55
Original line number Diff line number Diff line
@@ -34,10 +34,10 @@ import androidx.annotation.WorkerThread;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.launcher3.widget.custom.CustomWidgetManager;

import java.util.ArrayList;
import java.util.Collections;
@@ -67,11 +67,11 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {

    private static AppWidgetHost sWidgetHost = null;

    private final UpdateHandler mUpdateHandler = this::onWidgetUpdate;
    private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;

    private final @NonNull IntConsumer mAppWidgetRemovedCallback;

    private final ArrayList<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
    // Map to all pending updated keyed with appWidgetId;
    private final SparseArray<PendingUpdate> mPendingUpdateMap = new SparseArray<>();

@@ -175,7 +175,10 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
    @Override
    public void destroy() {
        try {
            MAIN_EXECUTOR.submit(() -> sHolders.remove(this)).get();
            MAIN_EXECUTOR.submit(() -> {
                clearViews();
                sHolders.remove(this);
            }).get();
        } catch (Exception e) {
            Log.e(TAG, "Failed to remove self from holder list", e);
        }
@@ -187,26 +190,6 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
                == (FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED);
    }

    /**
     * Add a listener that is triggered when the providers of the widgets are changed
     * @param listener The listener that notifies when the providers changed
     */
    @Override
    public void addProviderChangeListener(
            @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
        MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener));
    }

    /**
     * Remove the specified listener from the host
     * @param listener The listener that is to be removed from the host
     */
    @Override
    public void removeProviderChangeListener(
            LauncherWidgetHolder.ProviderChangedListener listener) {
        MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener));
    }

    /**
     * Stop the host from updating the widget views
     */
@@ -220,44 +203,41 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
        setListeningFlag(false);
    }

    /**
     * Create a view for the specified app widget
     * @param context The activity context for which the view is created
     * @param appWidgetId The ID of the widget
     * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
     * @return A view for the widget
     */
    @NonNull
    @Override
    public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
            @NonNull LauncherAppWidgetProviderInfo appWidget) {
        if (appWidget.isCustomWidget()) {
            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
            lahv.setAppWidget(appWidgetId, appWidget);
            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
            return lahv;
    public SafeCloseable addOnUpdateListener(int appWidgetId,
            LauncherAppWidgetProviderInfo appWidget, Runnable callback) {
        UpdateHandler handler = new UpdateHandler() {
            @Override
            public <T> void onWidgetUpdate(int widgetId, UpdateKey<T> key, T data) {
                if (KEY_VIEWS_UPDATE == key) {
                    callback.run();
                }

        LauncherAppWidgetHostView widgetView = getPendingView(appWidgetId);
        if (widgetView != null) {
            removePendingView(appWidgetId);
        } else {
            widgetView = new LauncherAppWidgetHostView(context);
            }
        };
        QuickstepWidgetHolderListener holderListener = getHolderListener(appWidgetId);
        holderListener.addHolder(handler);
        return () -> holderListener.mListeningHolders.remove(handler);
    }

    @NonNull
    @Override
    protected LauncherAppWidgetHostView createViewInternal(
            int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
        LauncherAppWidgetHostView widgetView = new LauncherAppWidgetHostView(mContext);
        widgetView.setInteractionHandler(mInteractionHandler);
        widgetView.setAppWidget(appWidgetId, appWidget);
        mViews.put(appWidgetId, widgetView);
        widgetView.updateAppWidget(getHolderListener(appWidgetId).addHolder(mUpdateHandler));
        return widgetView;
    }

    private static QuickstepWidgetHolderListener getHolderListener(int appWidgetId) {
        QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
        if (listener == null) {
            listener = new QuickstepWidgetHolderListener(appWidgetId);
            sWidgetHost.setListener(appWidgetId, listener);
            sListeners.put(appWidgetId, listener);
        }
        RemoteViews remoteViews = listener.addHolder(this);
        widgetView.updateAppWidget(remoteViews);

        return widgetView;
        return listener;
    }

    /**
@@ -267,7 +247,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
    public void clearViews() {
        mViews.clear();
        for (int i = sListeners.size() - 1; i >= 0; i--) {
            sListeners.valueAt(i).mListeningHolders.remove(this);
            sListeners.valueAt(i).mListeningHolders.remove(mUpdateHandler);
        }
    }

@@ -275,7 +255,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
            implements AppWidgetHost.AppWidgetHostListener {

        // Static listeners should use a set that is backed by WeakHashMap to avoid memory leak
        private final Set<QuickstepWidgetHolder> mListeningHolders = Collections.newSetFromMap(
        private final Set<UpdateHandler> mListeningHolders = Collections.newSetFromMap(
                new WeakHashMap<>());

        private final int mWidgetId;
@@ -288,7 +268,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {

        @UiThread
        @Nullable
        public RemoteViews addHolder(@NonNull QuickstepWidgetHolder holder) {
        public RemoteViews addHolder(@NonNull UpdateHandler holder) {
            mListeningHolders.add(holder);
            return mRemoteViews;
        }
@@ -359,11 +339,15 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
        }
    }

    private interface UpdateKey<T> extends BiConsumer<AppWidgetHostView, T> { }

    private interface UpdateHandler {
        <T> void onWidgetUpdate(int widgetId, UpdateKey<T> key, T data);
    }

    private static class PendingUpdate {
        public final IntSet changedViews = new IntSet();
        public AppWidgetProviderInfo providerInfo;
        public RemoteViews remoteViews;
    }

    private interface UpdateKey<T> extends BiConsumer<AppWidgetHostView, T> { }
}
+5 −10
Original line number Diff line number Diff line
@@ -1011,7 +1011,7 @@ public class Launcher extends StatefulActivity<LauncherState>
        AppWidgetHostView boundWidget = null;
        if (resultCode == RESULT_OK) {
            animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
            final AppWidgetHostView layout = mAppWidgetHolder.createView(this, appWidgetId,
            final AppWidgetHostView layout = mAppWidgetHolder.createView(appWidgetId,
                    requestArgs.getWidgetHandler().getProviderInfo(this));
            boundWidget = layout;
            onCompleteRunnable = () -> {
@@ -1464,7 +1464,7 @@ public class Launcher extends StatefulActivity<LauncherState>

        if (hostView == null) {
            // Perform actual inflation because we're live
            hostView = mAppWidgetHolder.createView(this, appWidgetId, appWidgetInfo);
            hostView = mAppWidgetHolder.createView(appWidgetId, appWidgetInfo);
        }

        LauncherAppWidgetInfo launcherInfo;
@@ -2319,7 +2319,7 @@ public class Launcher extends StatefulActivity<LauncherState>
        }
        final AppWidgetHostView view;
        if (mIsSafeModeEnabled) {
            view = new PendingAppWidgetHostView(this, item, mIconCache, true);
            view = new PendingAppWidgetHostView(this, item, null, true);
            prepareAppWidget(view, item);
            return view;
        }
@@ -2450,14 +2450,9 @@ public class Launcher extends StatefulActivity<LauncherState>

                item.minSpanX = appWidgetInfo.minSpanX;
                item.minSpanY = appWidgetInfo.minSpanY;
                view = mAppWidgetHolder.createView(this, item.appWidgetId, appWidgetInfo);
            } else if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
                    && appWidgetInfo != null) {
                mAppWidgetHolder.addPendingView(item.appWidgetId,
                        new PendingAppWidgetHostView(this, item, mIconCache, false));
                view = mAppWidgetHolder.createView(this, item.appWidgetId, appWidgetInfo);
                view = mAppWidgetHolder.createView(item.appWidgetId, appWidgetInfo);
            } else {
                view = new PendingAppWidgetHostView(this, item, mIconCache, false);
                view = new PendingAppWidgetHostView(this, item, appWidgetInfo, false);
            }
            prepareAppWidget(view, item);
        } finally {
+50 −27
Original line number Diff line number Diff line
@@ -21,13 +21,22 @@ import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_I
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.launcher3.LauncherAppState;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.IntConsumer;

/**
@@ -37,8 +46,7 @@ import java.util.function.IntConsumer;
 */
class LauncherAppWidgetHost extends AppWidgetHost {
    @NonNull
    private final ArrayList<LauncherWidgetHolder.ProviderChangedListener>
            mProviderChangeListeners = new ArrayList<>();
    private final List<ProviderChangedListener> mProviderChangeListeners;

    @NonNull
    private final Context mContext;
@@ -46,33 +54,13 @@ class LauncherAppWidgetHost extends AppWidgetHost {
    @Nullable
    private final IntConsumer mAppWidgetRemovedCallback;

    @NonNull
    private final LauncherWidgetHolder mHolder;

    public LauncherAppWidgetHost(@NonNull Context context,
            @Nullable IntConsumer appWidgetRemovedCallback, @NonNull LauncherWidgetHolder holder) {
            @Nullable IntConsumer appWidgetRemovedCallback,
            List<ProviderChangedListener> providerChangeListeners) {
        super(context, APPWIDGET_HOST_ID);
        mContext = context;
        mAppWidgetRemovedCallback = appWidgetRemovedCallback;
        mHolder = holder;
    }

    /**
     * Add a listener that is triggered when the providers of the widgets are changed
     * @param listener The listener that notifies when the providers changed
     */
    public void addProviderChangeListener(
            @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
        mProviderChangeListeners.add(listener);
    }

    /**
     * Remove the specified listener from the host
     * @param listener The listener that is to be removed from the host
     */
    public void removeProviderChangeListener(
            LauncherWidgetHolder.ProviderChangedListener listener) {
        mProviderChangeListeners.remove(listener);
        mProviderChangeListeners = providerChangeListeners;
    }

    @Override
@@ -89,7 +77,7 @@ class LauncherAppWidgetHost extends AppWidgetHost {
    @NonNull
    public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
            AppWidgetProviderInfo appWidget) {
        return mHolder.onCreateView(context, appWidgetId);
        return new ListenableHostView(context);
    }

    /**
@@ -115,7 +103,10 @@ class LauncherAppWidgetHost extends AppWidgetHost {
        if (mAppWidgetRemovedCallback == null) {
            return;
        }
        mAppWidgetRemovedCallback.accept(appWidgetId);
        // Route the call via model thread, in case it comes while a loader-bind is in progress
        Executors.MODEL_EXECUTOR.execute(
                () -> Executors.MAIN_EXECUTOR.execute(
                        () -> mAppWidgetRemovedCallback.accept(appWidgetId)));
    }

    /**
@@ -126,4 +117,36 @@ class LauncherAppWidgetHost extends AppWidgetHost {
        super.clearViews();
    }

    public static class ListenableHostView extends LauncherAppWidgetHostView {

        private Set<Runnable> mUpdateListeners = Collections.EMPTY_SET;

        ListenableHostView(Context context) {
            super(context);
        }

        @Override
        public void updateAppWidget(RemoteViews remoteViews) {
            super.updateAppWidget(remoteViews);
            mUpdateListeners.forEach(Runnable::run);
        }

        @Override
        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
            super.onInitializeAccessibilityNodeInfo(info);
            info.setClassName(LauncherAppWidgetHostView.class.getName());
        }

        /**
         * Adds a callback to be run everytime the provided app widget updates.
         * @return a closable to remove this callback
         */
        public SafeCloseable addUpdateListener(Runnable callback) {
            if (mUpdateListeners == Collections.EMPTY_SET) {
                mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>());
            }
            mUpdateListeners.add(callback);
            return () -> mUpdateListeners.remove(callback);
        }
    }
}
+48 −67
Original line number Diff line number Diff line
@@ -42,8 +42,12 @@ import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;

import java.util.ArrayList;
import java.util.List;
import java.util.function.IntConsumer;

/**
@@ -61,15 +65,14 @@ public class LauncherWidgetHolder {
            FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED;

    @NonNull
    private final Context mContext;
    protected final Context mContext;

    @NonNull
    private final AppWidgetHost mWidgetHost;

    @NonNull
    protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
    @NonNull
    private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
    protected final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();

    protected int mFlags = FLAG_STATE_IS_NORMAL;

@@ -86,7 +89,8 @@ public class LauncherWidgetHolder {

    protected AppWidgetHost createHost(
            Context context, @Nullable IntConsumer appWidgetRemovedCallback) {
        return new LauncherAppWidgetHost(context, appWidgetRemovedCallback, this);
        return new LauncherAppWidgetHost(
                context, appWidgetRemovedCallback, mProviderChangedListeners);
    }

    /**
@@ -157,28 +161,6 @@ public class LauncherWidgetHolder {
        mViews.remove(appWidgetId);
    }

    /**
     * Add the pending view to the host for complete configuration in further steps
     * @param appWidgetId The ID of the specified app widget
     * @param view The {@link PendingAppWidgetHostView} of the app widget
     */
    public void addPendingView(int appWidgetId, @NonNull PendingAppWidgetHostView view) {
        mPendingViews.put(appWidgetId, view);
    }

    /**
     * @param appWidgetId The app widget id of the specified widget
     * @return The {@link PendingAppWidgetHostView} of the widget if it exists, null otherwise
     */
    @Nullable
    protected PendingAppWidgetHostView getPendingView(int appWidgetId) {
        return mPendingViews.get(appWidgetId);
    }

    protected void removePendingView(int appWidgetId) {
        mPendingViews.remove(appWidgetId);
    }

    /**
     * Called when the launcher is destroyed
     */
@@ -201,18 +183,18 @@ public class LauncherWidgetHolder {
     * Add a listener that is triggered when the providers of the widgets are changed
     * @param listener The listener that notifies when the providers changed
     */
    public void addProviderChangeListener(@NonNull ProviderChangedListener listener) {
        LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
        tempHost.addProviderChangeListener(listener);
    public void addProviderChangeListener(
            @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
        MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener));
    }

    /**
     * Remove the specified listener from the host
     * @param listener The listener that is to be removed from the host
     */
    public void removeProviderChangeListener(ProviderChangedListener listener) {
        LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
        tempHost.removeProviderChangeListener(listener);
    public void removeProviderChangeListener(
            LauncherWidgetHolder.ProviderChangedListener listener) {
        MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener));
    }

    /**
@@ -315,33 +297,52 @@ public class LauncherWidgetHolder {
        return mWidgetHost.getAppWidgetIds();
    }

    /**
     * Adds a callback to be run everytime the provided app widget updates.
     * @return a closable to remove this callback
     */
    public SafeCloseable addOnUpdateListener(
            int appWidgetId, LauncherAppWidgetProviderInfo appWidget, Runnable callback) {
        if (createView(appWidgetId, appWidget) instanceof ListenableHostView lhv) {
            return lhv.addUpdateListener(callback);
        }
        return () -> { };
    }

    /**
     * Create a view for the specified app widget
     * @param context The activity context for which the view is created
     *
     * @param appWidgetId The ID of the widget
     * @param appWidget   The {@link LauncherAppWidgetProviderInfo} of the widget
     * @return A view for the widget
     */
    @NonNull
    public AppWidgetHostView createView(@NonNull Context context, int appWidgetId,
            @NonNull LauncherAppWidgetProviderInfo appWidget) {

    public AppWidgetHostView createView(
            int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
        if (appWidget.isCustomWidget()) {
            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(mContext);
            lahv.setAppWidget(0, appWidget);
            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
            CustomWidgetManager.INSTANCE.get(mContext).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.
            LauncherAppWidgetHostView view =
                    new PendingAppWidgetHostView(context, appWidgetId, appWidget);
        }

        LauncherAppWidgetHostView view = createViewInternal(appWidgetId, appWidget);
        mViews.put(appWidgetId, view);
        return view;
    }

    @NonNull
    protected LauncherAppWidgetHostView createViewInternal(
            int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
        if ((mFlags & FLAG_LISTENING) == 0) {
            // Since the launcher hasn't started listening to widget updates, we can't simply call
            // host.createView here because the later will make a binder call to retrieve
            // RemoteViews from system process.
            return new PendingAppWidgetHostView(mContext, appWidgetId, appWidget);
        } else {
            try {
                return mWidgetHost.createView(context, appWidgetId, appWidget);
                return (LauncherAppWidgetHostView) mWidgetHost.createView(
                        mContext, appWidgetId, appWidget);
            } catch (Exception e) {
                if (!Utilities.isBinderSizeError(e)) {
                    throw new RuntimeException(e);
@@ -352,7 +353,7 @@ public class LauncherWidgetHolder {
                // will update.
                LauncherAppWidgetHostView view = mViews.get(appWidgetId);
                if (view == null) {
                    view = onCreateView(mContext, appWidgetId);
                    view = new ListenableHostView(mContext);
                }
                view.setAppWidget(appWidgetId, appWidget);
                view.switchToErrorView();
@@ -371,26 +372,6 @@ public class LauncherWidgetHolder {
        void notifyWidgetProvidersChanged();
    }

    /**
     * Called to return a proper view when creating a view
     *
     * @param context     The context for which the widget view is created
     * @param appWidgetId The ID of the added widget
     * @return A view for the specified app widget
     */
    @NonNull
    public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId) {
        final LauncherAppWidgetHostView view;
        if (getPendingView(appWidgetId) != null) {
            view = getPendingView(appWidgetId);
            removePendingView(appWidgetId);
        } else {
            view = new LauncherAppWidgetHostView(context);
        }
        mViews.put(appWidgetId, view);
        return view;
    }

    /**
     * Clears all the views from the host
     */
Loading