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

Commit 40e7937a authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Simplifying pending widget inflation path" into main

parents 225bf586 fdeb19e0
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