Loading quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java +39 −55 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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<>(); Loading Loading @@ -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); } Loading @@ -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 */ Loading @@ -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; } /** Loading @@ -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); } } Loading @@ -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; Loading @@ -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; } Loading Loading @@ -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> { } } src/com/android/launcher3/Launcher.java +5 −10 Original line number Diff line number Diff line Loading @@ -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 = () -> { Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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 { Loading src/com/android/launcher3/widget/LauncherAppWidgetHost.java +50 −27 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; Loading @@ -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 Loading @@ -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); } /** Loading @@ -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))); } /** Loading @@ -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); } } } src/com/android/launcher3/widget/LauncherWidgetHolder.java +48 −67 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; Loading @@ -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); } /** Loading Loading @@ -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 */ Loading @@ -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)); } /** Loading Loading @@ -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); Loading @@ -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(); Loading @@ -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 Loading
quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading
quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java +39 −55 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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<>(); Loading Loading @@ -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); } Loading @@ -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 */ Loading @@ -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; } /** Loading @@ -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); } } Loading @@ -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; Loading @@ -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; } Loading Loading @@ -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> { } }
src/com/android/launcher3/Launcher.java +5 −10 Original line number Diff line number Diff line Loading @@ -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 = () -> { Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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 { Loading
src/com/android/launcher3/widget/LauncherAppWidgetHost.java +50 −27 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; Loading @@ -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 Loading @@ -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); } /** Loading @@ -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))); } /** Loading @@ -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); } } }
src/com/android/launcher3/widget/LauncherWidgetHolder.java +48 −67 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; Loading @@ -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); } /** Loading Loading @@ -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 */ Loading @@ -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)); } /** Loading Loading @@ -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); Loading @@ -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(); Loading @@ -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