diff options
author | 2025-03-13 23:20:29 -0700 | |
---|---|---|
committer | 2025-03-14 14:19:29 -0700 | |
commit | 66916cda10669dd0517615039b6d160bbf11c62a (patch) | |
tree | 55027bb3e12f73b72146a31345b163bdd21cec58 /src | |
parent | 0c762ac84f78c7d700a6c20540bd7bef571c0364 (diff) |
Moving LauncherAppWidgetHolder to dagger
This would allow customizing the widget holder in LauncherPreview
Bug: 361850561
Test: Updated tests and presubmit
Flag: EXEMPT dagger
Change-Id: I32491169188992453693048986c57cb780fdf1d8
Diffstat (limited to 'src')
8 files changed, 163 insertions, 160 deletions
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 5c9392d69d..d5b3ed5645 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -532,11 +532,14 @@ public class Launcher extends StatefulActivity<LauncherState> mAllAppsController = new AllAppsTransitionController(this); mStateManager = new StateManager<>(this, NORMAL); + mAppWidgetManager = new WidgetManagerHelper(this); + mAppWidgetHolder = LauncherWidgetHolder.newInstance(this); + mAppWidgetHolder.setAppWidgetRemovedCallback( + appWidgetId -> getWorkspace().removeWidget(appWidgetId)); + setupViews(); updateDisallowBack(); - mAppWidgetManager = new WidgetManagerHelper(this); - mAppWidgetHolder = createAppWidgetHolder(); mAppWidgetHolder.startListening(); mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null)); mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(), @@ -1614,11 +1617,6 @@ public class Launcher extends StatefulActivity<LauncherState> return instance; } - protected LauncherWidgetHolder createAppWidgetHolder() { - return LauncherWidgetHolder.HolderFactory.newFactory(this).newInstance( - this, appWidgetId -> getWorkspace().removeWidget(appWidgetId)); - } - @Override protected void onNewIntent(Intent intent) { if (Utilities.isRunningInTestHarness()) { diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 44dcc06791..d987841410 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -163,11 +163,6 @@ public final class FeatureFlags { "ENABLE_WIDGET_TRANSITION_FOR_RESIZING", DISABLED, "Enable widget transition animation when resizing the widgets"); - // TODO(Block 25): Clean up flags - public static final BooleanFlag ENABLE_WIDGET_HOST_IN_BACKGROUND = getDebugFlag(270394384, - "ENABLE_WIDGET_HOST_IN_BACKGROUND", ENABLED, - "Enable background widget updates listening for widget holder"); - // TODO(Block 27): Clean up flags public static final BooleanFlag ENABLE_OVERLAY_CONNECTION_OPTIM = getDebugFlag(270392629, "ENABLE_OVERLAY_CONNECTION_OPTIM", DISABLED, diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java index c58a414304..0fd32190c4 100644 --- a/src/com/android/launcher3/dagger/LauncherAppModule.java +++ b/src/com/android/launcher3/dagger/LauncherAppModule.java @@ -23,6 +23,7 @@ import dagger.Module; ApiWrapperModule.class, PluginManagerWrapperModule.class, StaticObjectModule.class, + WidgetModule.class, AppModule.class }) public class LauncherAppModule { diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java index c49909772e..f86772e5f5 100644 --- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java +++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java @@ -46,6 +46,7 @@ import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.util.WallpaperColorHints; import com.android.launcher3.util.window.RefreshRateTracker; import com.android.launcher3.util.window.WindowManagerProxy; +import com.android.launcher3.widget.LauncherWidgetHolder.WidgetHolderFactory; import com.android.launcher3.widget.custom.CustomWidgetManager; import dagger.BindsInstance; @@ -89,6 +90,7 @@ public interface LauncherBaseAppComponent { WidgetsFilterDataProvider getWidgetsFilterDataProvider(); LoaderCursorFactory getLoaderCursorFactory(); + WidgetHolderFactory getWidgetHolderFactory(); /** Builder for LauncherBaseAppComponent. */ interface Builder { diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java index 91b899c2ef..63d2954e70 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java @@ -16,8 +16,6 @@ package com.android.launcher3.widget; -import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; - import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; @@ -26,49 +24,18 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.util.Executors; -import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.IntConsumer; - /** * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView} * which correctly captures all long-press events. This ensures that users can * always pick up and move widgets. */ -class LauncherAppWidgetHost extends AppWidgetHost { - @NonNull - private final List<ProviderChangedListener> mProviderChangeListeners; - - @NonNull - private final Context mContext; - - @Nullable - private final IntConsumer mAppWidgetRemovedCallback; +class LauncherAppWidgetHost extends ListenableAppWidgetHost { @Nullable private ListenableHostView mViewToRecycle; - public LauncherAppWidgetHost(@NonNull Context context, - @Nullable IntConsumer appWidgetRemovedCallback, - List<ProviderChangedListener> providerChangeListeners) { - super(context, APPWIDGET_HOST_ID); - mContext = context; - mAppWidgetRemovedCallback = appWidgetRemovedCallback; - mProviderChangeListeners = providerChangeListeners; - } - - @Override - protected void onProvidersChanged() { - if (!mProviderChangeListeners.isEmpty()) { - for (LauncherWidgetHolder.ProviderChangedListener callback : - new ArrayList<>(mProviderChangeListeners)) { - callback.notifyWidgetProvidersChanged(); - } - } + LauncherAppWidgetHost(@NonNull Context context, int appWidgetId) { + super(context, appWidgetId); } /** @@ -94,35 +61,6 @@ class LauncherAppWidgetHost extends AppWidgetHost { } /** - * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. - */ - @Override - protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) { - LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo( - mContext, appWidget); - super.onProviderChanged(appWidgetId, info); - // The super method updates the dimensions of the providerInfo. Update the - // launcher spans accordingly. - info.initSpans(mContext, LauncherAppState.getIDP(mContext)); - } - - /** - * Called on an appWidget is removed for a widgetId - * - * @param appWidgetId TODO: make this override when SDK is updated - */ - @Override - public void onAppWidgetRemoved(int appWidgetId) { - if (mAppWidgetRemovedCallback == null) { - return; - } - // 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))); - } - - /** * The same as super.clearViews(), except with the scope exposed */ @Override diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java index 78197e2dd0..642f35abcd 100644 --- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java +++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java @@ -20,10 +20,9 @@ import static android.app.Activity.RESULT_CANCELED; import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED; import static com.android.launcher3.Flags.enableWorkspaceInflation; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo; +import static com.android.launcher3.widget.ListenableAppWidgetHost.getWidgetHolderExecutor; -import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; @@ -32,6 +31,7 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Looper; +import android.util.Log; import android.util.SparseArray; import android.widget.Toast; @@ -43,18 +43,23 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.BaseActivity; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.dagger.LauncherComponentProvider; 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.LooperExecutor; -import com.android.launcher3.util.ResourceBasedOverride; import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.widget.ListenableAppWidgetHost.ProviderChangedListener; import com.android.launcher3.widget.custom.CustomWidgetManager; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -62,51 +67,57 @@ import java.util.function.IntConsumer; * background. */ public class LauncherWidgetHolder { + + private static final String TAG = "LauncherWidgetHolder"; + public static final int APPWIDGET_HOST_ID = 1024; protected static final int FLAG_LISTENING = 1; protected static final int FLAG_STATE_IS_NORMAL = 1 << 1; protected static final int FLAG_ACTIVITY_STARTED = 1 << 2; protected static final int FLAG_ACTIVITY_RESUMED = 1 << 3; + private static final int FLAGS_SHOULD_LISTEN = FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED; + // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden + private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"; + // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden + private static final int SPLASH_SCREEN_STYLE_EMPTY = 0; + @NonNull protected final Context mContext; @NonNull - private final AppWidgetHost mWidgetHost; + protected final ListenableAppWidgetHost mWidgetHost; @NonNull protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>(); - protected final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>(); + + /** package visibility */ + final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>(); protected AtomicInteger mFlags = new AtomicInteger(FLAG_STATE_IS_NORMAL); - // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden - private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"; - // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden - private static final int SPLASH_SCREEN_STYLE_EMPTY = 0; + @Nullable + private Consumer<LauncherAppWidgetHostView> mOnViewCreationCallback; - protected LauncherWidgetHolder(@NonNull Context context, - @Nullable IntConsumer appWidgetRemovedCallback) { - mContext = context; - mWidgetHost = createHost(context, appWidgetRemovedCallback); - } + /** package visibility */ + @Nullable IntConsumer mAppWidgetRemovedCallback; - protected AppWidgetHost createHost( - Context context, @Nullable IntConsumer appWidgetRemovedCallback) { - return new LauncherAppWidgetHost( - context, appWidgetRemovedCallback, mProviderChangedListeners); + @AssistedInject + protected LauncherWidgetHolder(@Assisted("UI_CONTEXT") @NonNull Context context) { + this(context, new LauncherAppWidgetHost(context, APPWIDGET_HOST_ID)); } - protected LooperExecutor getWidgetHolderExecutor() { - return UI_HELPER_EXECUTOR; + protected LauncherWidgetHolder( + @NonNull Context context, @NonNull ListenableAppWidgetHost appWidgetHost) { + mContext = context; + mWidgetHost = appWidgetHost; + MAIN_EXECUTOR.execute(() -> mWidgetHost.getHolders().add(this)); } - /** - * Starts listening to the widget updates from the server side - */ + /** Starts listening to the widget updates from the server side */ public void startListening() { if (!WIDGETS_ENABLED) { return; @@ -127,13 +138,11 @@ public class LauncherWidgetHolder { // TODO: Investigate why widgetHost.startListening() always return non-empty updates setListeningFlag(true); - MAIN_EXECUTOR.execute(() -> updateDeferredView()); + MAIN_EXECUTOR.execute(this::updateDeferredView); }); } - /** - * Update any views which have been deferred because the host was not listening. - */ + /** Update any views which have been deferred because the host was not listening */ protected void updateDeferredView() { // Update any views which have been deferred because the host was not listening. // We go in reverse order and inflate any deferred or cached widget @@ -180,7 +189,14 @@ public class LauncherWidgetHolder { * Called when the launcher is destroyed */ public void destroy() { - // No-op + try { + MAIN_EXECUTOR.submit(() -> { + clearViews(); + mWidgetHost.getHolders().remove(this); + }).get(); + } catch (Exception e) { + Log.e(TAG, "Failed to remove self from holder list", e); + } } /** @@ -198,8 +214,7 @@ 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 LauncherWidgetHolder.ProviderChangedListener listener) { + public void addProviderChangeListener(@NonNull ProviderChangedListener listener) { MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener)); } @@ -207,12 +222,23 @@ public class LauncherWidgetHolder { * 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) { + public void removeProviderChangeListener(ProviderChangedListener listener) { MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener)); } /** + * Sets a callbacks for whenever a widget view is created + */ + public void setOnViewCreationCallback(@Nullable Consumer<LauncherAppWidgetHostView> callback) { + mOnViewCreationCallback = callback; + } + + /** Sets a callback for listening app widget removals */ + public void setAppWidgetRemovedCallback(@Nullable IntConsumer callback) { + mAppWidgetRemovedCallback = callback; + } + + /** * Starts the configuration activity for the widget * @param activity The activity in which to start the configuration page * @param widgetId The ID of the widget @@ -284,9 +310,7 @@ public class LauncherWidgetHolder { activity.startActivityForResult(intent, requestCode); } - /** - * Stop the host from listening to the widget updates - */ + /** Stop the host from listening to the widget updates */ public void stopListening() { if (!WIDGETS_ENABLED) { return; @@ -298,8 +322,8 @@ public class LauncherWidgetHolder { } /** - * Update {@link FLAG_LISTENING} on {@link mFlags} after making binder calls from - * {@link sWidgetHost}. + * Update {@link #FLAG_LISTENING} on {@link #mFlags} after making binder calls from + * {@link #mWidgetHost}. */ @WorkerThread protected void setListeningFlag(final boolean isListening) { @@ -350,6 +374,7 @@ public class LauncherWidgetHolder { } LauncherAppWidgetHostView view = createViewInternal(appWidgetId, appWidget); + if (mOnViewCreationCallback != null) mOnViewCreationCallback.accept(view); // Do not update mViews on a background thread call, as the holder is not thread safe. if (!enableWorkspaceInflation() || Looper.myLooper() == Looper.getMainLooper()) { mViews.put(appWidgetId, view); @@ -368,8 +393,8 @@ public class LauncherWidgetHolder { // Binder can also inflate placeholder widgets in case of backup-restore. Skip // attaching such widgets - boolean isRealWidget = ((view instanceof PendingAppWidgetHostView pw) - ? pw.isDeferredWidget() : true) + boolean isRealWidget = (!(view instanceof PendingAppWidgetHostView pw) + || pw.isDeferredWidget()) && view.getAppWidgetInfo() != null; if (isRealWidget && mViews.get(view.getAppWidgetId()) != view) { view = recycleExistingView(view); @@ -446,28 +471,13 @@ public class LauncherWidgetHolder { } } - /** - * Listener for getting notifications on provider changes. - */ - public interface ProviderChangedListener { - /** - * Notify the listener that the providers have changed - */ - void notifyWidgetProvidersChanged(); - } - - /** - * Clears all the views from the host - */ + /** Clears all the views from the host */ public void clearViews() { - LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost; - tempHost.clearViews(); + ((LauncherAppWidgetHost) mWidgetHost).clearViews(); mViews.clear(); } - /** - * Clears all the internal widget views - */ + /** Clears all the internal widget views */ public void clearWidgetViews() { clearViews(); } @@ -514,32 +524,19 @@ public class LauncherWidgetHolder { * Returns the new LauncherWidgetHolder instance */ public static LauncherWidgetHolder newInstance(Context context) { - return HolderFactory.newFactory(context).newInstance(context, null); + return LauncherComponentProvider.get(context).getWidgetHolderFactory().newInstance(context); } - /** - * A factory class that generates new instances of {@code LauncherWidgetHolder} - */ - public static class HolderFactory implements ResourceBasedOverride { - - /** - * @param context The context of the caller - * @param appWidgetRemovedCallback The callback that is called when widgets are removed - * @return A new instance of {@code LauncherWidgetHolder} - */ - public LauncherWidgetHolder newInstance(@NonNull Context context, - @Nullable IntConsumer appWidgetRemovedCallback) { - return new LauncherWidgetHolder(context, appWidgetRemovedCallback); - } + /** A factory that generates new instances of {@code LauncherWidgetHolder} */ + public interface WidgetHolderFactory { - /** - * @param context The context of the caller - * @return A new instance of factory class for widget holders. If not specified, returning - * {@code HolderFactory} by default. - */ - public static HolderFactory newFactory(Context context) { - return Overrides.getObject( - HolderFactory.class, context, R.string.widget_holder_factory_class); - } + LauncherWidgetHolder newInstance(@NonNull Context context); + } + + /** A factory that generates new instances of {@code LauncherWidgetHolder} */ + @AssistedFactory + public interface WidgetHolderFactoryImpl extends WidgetHolderFactory { + + LauncherWidgetHolder newInstance(@Assisted("UI_CONTEXT") @NonNull Context context); } } diff --git a/src/com/android/launcher3/widget/ListenableAppWidgetHost.kt b/src/com/android/launcher3/widget/ListenableAppWidgetHost.kt new file mode 100644 index 0000000000..58bf0aa869 --- /dev/null +++ b/src/com/android/launcher3/widget/ListenableAppWidgetHost.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.widget + +import android.appwidget.AppWidgetHost +import android.appwidget.AppWidgetProviderInfo +import android.content.Context +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.util.Executors +import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import com.android.launcher3.util.Executors.MODEL_EXECUTOR +import com.android.launcher3.util.LooperExecutor + +open class ListenableAppWidgetHost(private val ctx: Context, hostId: Int) : + AppWidgetHost(ctx, hostId) { + + protected val holders = mutableListOf<LauncherWidgetHolder>() + + override fun onProvidersChanged() { + MAIN_EXECUTOR.execute { + holders.forEach { holder -> + // Listeners might remove themselves from the list during the iteration. + // Creating a copy of the list to avoid exceptions for concurrent modification. + holder.mProviderChangedListeners.toList().forEach { + it.notifyWidgetProvidersChanged() + } + } + } + } + + override fun onAppWidgetRemoved(appWidgetId: Int) { + // Route the call via model thread, in case it comes while a loader-bind is in progress + MODEL_EXECUTOR.execute { + MAIN_EXECUTOR.execute { + holders.forEach { it.mAppWidgetRemovedCallback?.accept(appWidgetId) } + } + } + } + + override fun onProviderChanged(appWidgetId: Int, appWidget: AppWidgetProviderInfo) { + val info = LauncherAppWidgetProviderInfo.fromProviderInfo(ctx, appWidget) + super.onProviderChanged(appWidgetId, info) + // The super method updates the dimensions of the providerInfo. Update the + // launcher spans accordingly. + info.initSpans(ctx, InvariantDeviceProfile.INSTANCE.get(ctx)) + } + + /** Listener for getting notifications on provider changes. */ + fun interface ProviderChangedListener { + /** Notify the listener that the providers have changed */ + fun notifyWidgetProvidersChanged() + } + + companion object { + + @JvmStatic val widgetHolderExecutor: LooperExecutor = Executors.UI_HELPER_EXECUTOR + } +} diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index cd8e4571e5..1c29f8907f 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -66,7 +66,7 @@ import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.Themes; -import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; +import com.android.launcher3.widget.ListenableAppWidgetHost.ProviderChangedListener; import java.util.List; |