diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java index 729eea9834..379a6cd2f4 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java @@ -24,7 +24,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.Utilities; -import com.android.launcher3.widget.LauncherAppWidgetHost; +import com.android.launcher3.widget.LauncherWidgetHolder; /** * A wrapper for the hidden API calls @@ -44,7 +44,7 @@ public class ApiWrapper { * @param handler InteractionHandler for the views in the host */ public static void setHostInteractionHandler(@NonNull AppWidgetHost host, - @Nullable LauncherAppWidgetHost.LauncherWidgetInteractionHandler handler) { + @Nullable LauncherWidgetHolder.LauncherWidgetInteractionHandler handler) { host.setInteractionHandler(handler::onInteraction); } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java index 2dde6b6148..353d817242 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java @@ -33,12 +33,12 @@ import com.android.launcher3.Utilities; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.ActivityOptionsWrapper; -import com.android.launcher3.widget.LauncherAppWidgetHost; import com.android.launcher3.widget.LauncherAppWidgetHostView; +import com.android.launcher3.widget.LauncherWidgetHolder; /** Provides a Quickstep specific animation when launching an activity from an app widget. */ -class QuickstepInteractionHandler implements - LauncherAppWidgetHost.LauncherWidgetInteractionHandler { +class QuickstepInteractionHandler + implements LauncherWidgetHolder.LauncherWidgetInteractionHandler { private static final String TAG = "QuickstepInteractionHandler"; diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 9813c8a987..f90feb8ed7 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -78,7 +78,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherWidgetHolder; import com.android.launcher3.QuickstepAccessibilityDelegate; import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; @@ -125,6 +124,7 @@ import com.android.launcher3.util.PendingSplitSelectInfo; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.util.TouchController; +import com.android.launcher3.widget.LauncherWidgetHolder; import com.android.quickstep.OverviewCommandHelper; import com.android.quickstep.RecentsModel; import com.android.quickstep.SystemUiProxy; diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java index 75e89b2944..eb6d096602 100644 --- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java +++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java @@ -2,7 +2,6 @@ package com.android.launcher3; import static android.os.Process.myUserHandle; -import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; @@ -12,6 +11,7 @@ import android.content.Intent; import android.database.Cursor; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import com.android.launcher3.LauncherSettings.Favorites; @@ -21,7 +21,7 @@ import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.pm.UserCache; import com.android.launcher3.provider.RestoreDbTask; import com.android.launcher3.util.ContentWriter; -import com.android.launcher3.widget.LauncherAppWidgetHost; +import com.android.launcher3.widget.LauncherWidgetHolder; public class AppWidgetsRestoredReceiver extends BroadcastReceiver { @@ -32,7 +32,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { if (AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED.equals(intent.getAction())) { int hostId = intent.getIntExtra(AppWidgetManager.EXTRA_HOST_ID, 0); Log.d(TAG, "Widget ID map received for host:" + hostId); - if (hostId != LauncherAppWidgetHost.APPWIDGET_HOST_ID) { + if (hostId != LauncherWidgetHolder.APPWIDGET_HOST_ID) { return; } @@ -50,11 +50,11 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { * Updates the app widgets whose id has changed during the restore process. */ @WorkerThread - public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds) { - AppWidgetHost appWidgetHost = new LauncherAppWidgetHost(context); + public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds, + @NonNull LauncherWidgetHolder holder) { if (WidgetsModel.GO_DISABLE_WIDGETS) { Log.e(TAG, "Skipping widget ID remap as widgets not supported"); - appWidgetHost.deleteHost(); + holder.deleteHost(); return; } if (!RestoreDbTask.isPending(context)) { @@ -63,7 +63,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { Log.e(TAG, "Skipping widget ID remap as DB already in use"); for (int widgetId : newWidgetIds) { Log.d(TAG, "Deleting widgetId: " + widgetId); - appWidgetHost.deleteAppWidgetId(widgetId); + holder.deleteAppWidgetId(widgetId); } return; } @@ -100,7 +100,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { try { if (!cursor.moveToFirst()) { // The widget no long exists. - appWidgetHost.deleteAppWidgetId(newWidgetIds[i]); + holder.deleteAppWidgetId(newWidgetIds[i]); } } finally { cursor.close(); diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 64666b0041..efdd5e15e7 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -16,7 +16,6 @@ package com.android.launcher3; -import android.appwidget.AppWidgetHost; import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; @@ -48,6 +47,7 @@ import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Thunk; +import com.android.launcher3.widget.LauncherWidgetHolder; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -74,7 +74,7 @@ public class AutoInstallsLayout { private static final String FORMATTED_LAYOUT_RES = "default_layout_%dx%d"; private static final String LAYOUT_RES = "default_layout"; - static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost, + static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWidgetHolder, LayoutParserCallback callback) { Pair customizationApkInfo = PackageManagerHelper.findSystemApk( ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager()); @@ -109,7 +109,7 @@ public class AutoInstallsLayout { Log.e(TAG, "Layout definition not found in package: " + pkg); return null; } - return new AutoInstallsLayout(context, appWidgetHost, callback, targetRes, layoutId, + return new AutoInstallsLayout(context, appWidgetHolder, callback, targetRes, layoutId, TAG_WORKSPACE); } @@ -156,7 +156,7 @@ public class AutoInstallsLayout { @Thunk final Context mContext; @Thunk - final AppWidgetHost mAppWidgetHost; + final LauncherWidgetHolder mAppWidgetHolder; protected final LayoutParserCallback mCallback; protected final PackageManager mPackageManager; @@ -174,17 +174,17 @@ public class AutoInstallsLayout { protected SQLiteDatabase mDb; - public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost, + public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder, LayoutParserCallback callback, Resources res, int layoutId, String rootTag) { - this(context, appWidgetHost, callback, res, () -> res.getXml(layoutId), rootTag); + this(context, appWidgetHolder, callback, res, () -> res.getXml(layoutId), rootTag); } - public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost, + public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder, LayoutParserCallback callback, Resources res, Supplier initialLayoutSupplier, String rootTag) { mContext = context; - mAppWidgetHost = appWidgetHost; + mAppWidgetHolder = appWidgetHolder; mCallback = callback; mPackageManager = context.getPackageManager(); diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java index 4daca8b109..af13beac1d 100644 --- a/src/com/android/launcher3/DefaultLayoutParser.java +++ b/src/com/android/launcher3/DefaultLayoutParser.java @@ -1,6 +1,5 @@ package com.android.launcher3; -import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; @@ -21,6 +20,7 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.Thunk; +import com.android.launcher3.widget.LauncherWidgetHolder; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -55,9 +55,9 @@ public class DefaultLayoutParser extends AutoInstallsLayout { private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; - public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost, + public DefaultLayoutParser(Context context, LauncherWidgetHolder appWidgetHolder, LayoutParserCallback callback, Resources sourceRes, int layoutId) { - super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES); + super(context, appWidgetHolder, callback, sourceRes, layoutId, TAG_FAVORITES); } @Override @@ -336,11 +336,11 @@ public class DefaultLayoutParser extends AutoInstallsLayout { final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); int insertedId = -1; try { - int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + int appWidgetId = mAppWidgetHolder.allocateAppWidgetId(); if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) { Log.e(TAG, "Unable to bind app widget id " + cn); - mAppWidgetHost.deleteAppWidgetId(appWidgetId); + mAppWidgetHolder.deleteAppWidgetId(appWidgetId); return -1; } @@ -349,7 +349,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { mValues.put(Favorites._ID, mCallback.generateNewItemId()); insertedId = mCallback.insertAndCheck(mDb, mValues); if (insertedId < 0) { - mAppWidgetHost.deleteAppWidgetId(appWidgetId); + mAppWidgetHolder.deleteAppWidgetId(appWidgetId); return insertedId; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 8453f2c1dc..578efdf5db 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -210,6 +210,7 @@ import com.android.launcher3.views.OptionsPopupView; import com.android.launcher3.views.ScrimView; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.widget.LauncherWidgetHolder; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.PendingAppWidgetHostView; @@ -1712,6 +1713,7 @@ public class Launcher extends StatefulActivity } catch (NullPointerException ex) { Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex); } + mAppWidgetHolder.destroy(); TextKeyListener.getInstance().release(); clearPendingBinds(); diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 58e85fea7e..457e12673b 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -22,7 +22,6 @@ import static com.android.launcher3.provider.LauncherDbUtils.tableExists; import android.annotation.TargetApi; import android.app.backup.BackupManager; -import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.ContentProvider; @@ -55,6 +54,8 @@ import android.text.TextUtils; import android.util.Log; import android.util.Xml; +import androidx.annotation.NonNull; + import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.config.FeatureFlags; @@ -70,7 +71,7 @@ import com.android.launcher3.util.IntSet; import com.android.launcher3.util.NoLocaleSQLiteHelper; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Thunk; -import com.android.launcher3.widget.LauncherAppWidgetHost; +import com.android.launcher3.widget.LauncherWidgetHolder; import org.xmlpull.v1.XmlPullParser; @@ -255,17 +256,20 @@ public class LauncherProvider extends ContentProvider { values.getAsString(Favorites.APPWIDGET_PROVIDER)); if (cn != null) { + LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder(); try { - AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost(); - int appWidgetId = widgetHost.allocateAppWidgetId(); + int appWidgetId = widgetHolder.allocateAppWidgetId(); values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) { - widgetHost.deleteAppWidgetId(appWidgetId); + widgetHolder.deleteAppWidgetId(appWidgetId); return false; } } catch (RuntimeException e) { Log.e(TAG, "Failed to initialize external widget", e); return false; + } finally { + // Necessary to destroy the holder to free up possible activity context + widgetHolder.destroy(); } } else { return false; @@ -533,10 +537,10 @@ public class LauncherProvider extends ContentProvider { if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) { Log.d(TAG, "loading default workspace"); - AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost(); - AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost); + LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder(); + AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder); if (loader == null) { - loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper); + loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper); } if (loader == null) { final Partner partner = Partner.get(getContext().getPackageManager()); @@ -545,7 +549,7 @@ public class LauncherProvider extends ContentProvider { int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, "xml", partner.getPackageName()); if (workspaceResId != 0) { - loader = new DefaultLayoutParser(getContext(), widgetHost, + loader = new DefaultLayoutParser(getContext(), widgetHolder, mOpenHelper, partnerRes, workspaceResId); } } @@ -553,7 +557,7 @@ public class LauncherProvider extends ContentProvider { final boolean usingExternallyProvidedLayout = loader != null; if (loader == null) { - loader = getDefaultLayoutParser(widgetHost); + loader = getDefaultLayoutParser(widgetHolder); } // There might be some partially restored DB items, due to buggy restore logic in @@ -565,9 +569,10 @@ public class LauncherProvider extends ContentProvider { // Unable to load external layout. Cleanup and load the internal layout. mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), - getDefaultLayoutParser(widgetHost)); + getDefaultLayoutParser(widgetHolder)); } clearFlagEmptyDbCreated(); + widgetHolder.destroy(); } } @@ -576,7 +581,8 @@ public class LauncherProvider extends ContentProvider { * * @return the loader if the restrictions are set and the resource exists; null otherwise. */ - private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) { + private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction( + LauncherWidgetHolder widgetHolder) { Context ctx = getContext(); final String authority; if (!TextUtils.isEmpty(mProviderAuthority)) { @@ -602,7 +608,7 @@ public class LauncherProvider extends ContentProvider { parser.setInput(new StringReader(layout)); Log.d(TAG, "Loading layout from " + authority); - return new AutoInstallsLayout(ctx, widgetHost, mOpenHelper, + return new AutoInstallsLayout(ctx, widgetHolder, mOpenHelper, ctx.getPackageManager().getResourcesForApplication(pi.applicationInfo), () -> parser, AutoInstallsLayout.TAG_WORKSPACE); } catch (Exception e) { @@ -621,7 +627,7 @@ public class LauncherProvider extends ContentProvider { .build(); } - private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) { + private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) { InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext()); int defaultLayout = mUseTestWorkspaceLayout ? TEST_WORKSPACE_LAYOUT_RES_XML : idp.defaultLayoutId; @@ -631,7 +637,7 @@ public class LauncherProvider extends ContentProvider { defaultLayout = idp.demoModeLayoutId; } - return new DefaultLayoutParser(getContext(), widgetHost, + return new DefaultLayoutParser(getContext(), widgetHolder, mOpenHelper, getContext().getResources(), defaultLayout); } @@ -932,40 +938,46 @@ public class LauncherProvider extends ContentProvider { */ public void removeGhostWidgets(SQLiteDatabase db) { // Get all existing widget ids. - final AppWidgetHost host = newLauncherWidgetHost(); - final int[] allWidgets; + final LauncherWidgetHolder holder = newLauncherWidgetHolder(); try { - // Although the method was defined in O, it has existed since the beginning of time, - // so it might work on older platforms as well. - allWidgets = host.getAppWidgetIds(); - } catch (IncompatibleClassChangeError e) { - Log.e(TAG, "getAppWidgetIds not supported", e); - return; - } - final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db, - Favorites.TABLE_NAME, Favorites.APPWIDGET_ID, - "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null)); - boolean isAnyWidgetRemoved = false; - for (int widgetId : allWidgets) { - if (!validWidgets.contains(widgetId)) { - try { - FileLog.d(TAG, "Deleting invalid widget " + widgetId); - host.deleteAppWidgetId(widgetId); - isAnyWidgetRemoved = true; - } catch (RuntimeException e) { - // Ignore + final int[] allWidgets; + try { + // Although the method was defined in O, it has existed since the beginning of + // time, so it might work on older platforms as well. + allWidgets = holder.getAppWidgetIds(); + } catch (IncompatibleClassChangeError e) { + Log.e(TAG, "getAppWidgetIds not supported", e); + // Necessary to destroy the holder to free up possible activity context + holder.destroy(); + return; + } + final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db, + Favorites.TABLE_NAME, Favorites.APPWIDGET_ID, + "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null)); + boolean isAnyWidgetRemoved = false; + for (int widgetId : allWidgets) { + if (!validWidgets.contains(widgetId)) { + try { + FileLog.d(TAG, "Deleting invalid widget " + widgetId); + holder.deleteAppWidgetId(widgetId); + isAnyWidgetRemoved = true; + } catch (RuntimeException e) { + // Ignore + } } } - } - if (isAnyWidgetRemoved) { - final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf) - .collect(Collectors.joining(",", "[", "]")); - final String validWidgetsIds = Arrays.stream( - validWidgets.getArray().toArray()).mapToObj(String::valueOf) - .collect(Collectors.joining(",", "[", "]")); - FileLog.d(TAG, "One or more widgets was removed. db_path=" + db.getPath() - + " allWidgetsIds=" + allWidgetsIds - + ", validWidgetsIds=" + validWidgetsIds); + if (isAnyWidgetRemoved) { + final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf) + .collect(Collectors.joining(",", "[", "]")); + final String validWidgetsIds = Arrays.stream( + validWidgets.getArray().toArray()).mapToObj(String::valueOf) + .collect(Collectors.joining(",", "[", "]")); + FileLog.d(TAG, "One or more widgets was removed. db_path=" + db.getPath() + + " allWidgetsIds=" + allWidgetsIds + + ", validWidgetsIds=" + validWidgetsIds); + } + } finally { + holder.destroy(); } } @@ -1066,8 +1078,12 @@ public class LauncherProvider extends ContentProvider { return mMaxItemId; } - public AppWidgetHost newLauncherWidgetHost() { - return new LauncherAppWidgetHost(mContext); + /** + * @return A new {@link LauncherWidgetHolder} based on the current context + */ + @NonNull + public LauncherWidgetHolder newLauncherWidgetHolder() { + return new LauncherWidgetHolder(mContext); } @Override diff --git a/src/com/android/launcher3/LauncherWidgetHolder.java b/src/com/android/launcher3/LauncherWidgetHolder.java deleted file mode 100644 index 5fcd46fe4d..0000000000 --- a/src/com/android/launcher3/LauncherWidgetHolder.java +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (C) 2022 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; - -import android.appwidget.AppWidgetHostView; -import android.appwidget.AppWidgetProviderInfo; -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.uioverrides.ApiWrapper; -import com.android.launcher3.widget.LauncherAppWidgetHost; -import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; -import com.android.launcher3.widget.PendingAppWidgetHostView; - -import java.util.function.IntConsumer; - -/** - * A wrapper for LauncherAppWidgetHost. This class is created so the AppWidgetHost could run in - * background. - */ -public class LauncherWidgetHolder { - @NonNull - private final LauncherAppWidgetHost mWidgetHost; - - public LauncherWidgetHolder(@NonNull Context context) { - this(context, null); - } - - public LauncherWidgetHolder(@NonNull Context context, - @Nullable IntConsumer appWidgetRemovedCallback) { - mWidgetHost = new LauncherAppWidgetHost(context, appWidgetRemovedCallback); - } - - /** - * Starts listening to the widget updates from the server side - */ - public void startListening() { - mWidgetHost.startListening(); - } - - /** - * Set the STARTED state of the widget host - * @param isStarted True if setting the host as started, false otherwise - */ - public void setActivityStarted(boolean isStarted) { - mWidgetHost.setActivityStarted(isStarted); - } - - /** - * Set the RESUMED state of the widget host - * @param isResumed True if setting the host as resumed, false otherwise - */ - public void setActivityResumed(boolean isResumed) { - mWidgetHost.setActivityResumed(isResumed); - } - - /** - * Set the NORMAL state of the widget host - * @param isNormal True if setting the host to be in normal state, false otherwise - */ - public void setStateIsNormal(boolean isNormal) { - mWidgetHost.setStateIsNormal(isNormal); - } - - /** - * Delete the specified app widget from the host - * @param appWidgetId The ID of the app widget to be deleted - */ - public void deleteAppWidgetId(int appWidgetId) { - mWidgetHost.deleteAppWidgetId(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) { - mWidgetHost.addPendingView(appWidgetId, view); - } - - /** - * @return True if the host is listening to the widget updates, false otherwise - */ - public boolean isListening() { - return mWidgetHost.isListening(); - } - - /** - * @return The allocated app widget id if allocation is successful, returns -1 otherwise - */ - public int allocateAppWidgetId() { - return mWidgetHost.allocateAppWidgetId(); - } - - /** - * 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 LauncherAppWidgetHost.ProviderChangedListener listener) { - mWidgetHost.addProviderChangeListener(listener); - } - - /** - * Remove the specified listener from the host - * @param listener The listener that is to be removed from the host - */ - public void removeProviderChangeListener( - LauncherAppWidgetHost.ProviderChangedListener listener) { - mWidgetHost.removeProviderChangeListener(listener); - } - - /** - * 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 - * @param requestCode The request code - */ - public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId, - int requestCode) { - mWidgetHost.startConfigActivity(activity, widgetId, requestCode); - } - - /** - * Starts the binding flow for the widget - * @param activity The activity for which to bind the widget - * @param appWidgetId The ID of the widget - * @param info The {@link AppWidgetProviderInfo} of the widget - * @param requestCode The request code - */ - public void startBindFlow(@NonNull BaseActivity activity, - int appWidgetId, @NonNull AppWidgetProviderInfo info, int requestCode) { - mWidgetHost.startBindFlow(activity, appWidgetId, info, requestCode); - } - - /** - * Stop the host from listening to the widget updates - */ - public void stopListening() { - mWidgetHost.stopListening(); - } - - /** - * 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 info The {@link LauncherAppWidgetProviderInfo} of the widget - * @return A view for the widget - */ - @NonNull - public AppWidgetHostView createView(@NonNull Context context, int appWidgetId, - @NonNull LauncherAppWidgetProviderInfo info) { - return mWidgetHost.createView(context, appWidgetId, info); - } - - /** - * Set the interaction handler for the widget host - * @param handler The interaction handler - */ - public void setInteractionHandler( - @Nullable LauncherAppWidgetHost.LauncherWidgetInteractionHandler handler) { - ApiWrapper.setHostInteractionHandler(mWidgetHost, handler); - } - - /** - * Clears all the views from the host - */ - public void clearViews() { - mWidgetHost.clearViews(); - } -} diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index a8def69304..517bbf835a 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -107,8 +107,9 @@ import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.WallpaperOffsetInterpolator; -import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener; import com.android.launcher3.widget.LauncherAppWidgetHostView; +import com.android.launcher3.widget.LauncherWidgetHolder; +import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; import com.android.launcher3.widget.NavigableAppWidgetHostView; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java index 05b1984887..5a49f4ad41 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -53,6 +53,8 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.launcher3.BaseActivity; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; @@ -69,8 +71,8 @@ import com.android.launcher3.util.SystemUiController; import com.android.launcher3.views.AbstractSlideInView; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.widget.AddItemWidgetsBottomSheet; -import com.android.launcher3.widget.LauncherAppWidgetHost; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.widget.LauncherWidgetHolder; import com.android.launcher3.widget.NavigableAppWidgetHostView; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; @@ -105,7 +107,8 @@ public class AddItemActivity extends BaseActivity private WidgetCell mWidgetCell; // Widget request specific options. - private LauncherAppWidgetHost mAppWidgetHost; + @Nullable + private LauncherWidgetHolder mAppWidgetHolder = null; private WidgetManagerHelper mAppWidgetManager; private int mPendingBindWidgetId; private Bundle mWidgetOptions; @@ -284,7 +287,7 @@ public class AddItemActivity extends BaseActivity mWidgetCell.setRemoteViewsPreview(PinItemDragListener.getPreview(mRequest)); mAppWidgetManager = new WidgetManagerHelper(this); - mAppWidgetHost = new LauncherAppWidgetHost(this); + mAppWidgetHolder = new LauncherWidgetHolder(this); PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(widgetInfo, CONTAINER_PIN_WIDGETS); @@ -338,7 +341,7 @@ public class AddItemActivity extends BaseActivity return; } - mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId(); + mPendingBindWidgetId = mAppWidgetHolder.allocateAppWidgetId(); AppWidgetProviderInfo widgetProviderInfo = mRequest.getAppWidgetProviderInfo(this); boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( mPendingBindWidgetId, widgetProviderInfo, mWidgetOptions); @@ -349,7 +352,7 @@ public class AddItemActivity extends BaseActivity } // request bind widget - mAppWidgetHost.startBindFlow(this, mPendingBindWidgetId, + mAppWidgetHolder.startBindFlow(this, mPendingBindWidgetId, mRequest.getAppWidgetProviderInfo(this), REQUEST_BIND_APPWIDGET); } @@ -362,6 +365,15 @@ public class AddItemActivity extends BaseActivity mSlideInView.close(/* animate= */ true); } + @Override + public void onDestroy() { + super.onDestroy(); + if (mAppWidgetHolder != null) { + // Necessary to destroy the holder to free up possible activity context + mAppWidgetHolder.destroy(); + } + } + @Override public void onBackPressed() { logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_BACK); @@ -378,7 +390,7 @@ public class AddItemActivity extends BaseActivity acceptWidget(widgetId); } else { // Simply wait it out. - mAppWidgetHost.deleteAppWidgetId(widgetId); + mAppWidgetHolder.deleteAppWidgetId(widgetId); mPendingBindWidgetId = -1; } return; diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 482e923403..2361907ed4 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -99,8 +99,8 @@ import com.android.launcher3.util.window.WindowManagerProxy; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.widget.BaseLauncherAppWidgetHostView; -import com.android.launcher3.widget.LauncherAppWidgetHost; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.widget.LauncherWidgetHolder; import com.android.launcher3.widget.LocalColorExtractor; import com.android.launcher3.widget.NavigableAppWidgetHostView; import com.android.launcher3.widget.custom.CustomWidgetManager; @@ -554,7 +554,7 @@ public class LauncherPreviewRenderer extends ContextWrapper private class LauncherPreviewAppWidgetHost extends AppWidgetHost { private LauncherPreviewAppWidgetHost(Context context) { - super(context, LauncherAppWidgetHost.APPWIDGET_HOST_ID); + super(context, LauncherWidgetHolder.APPWIDGET_HOST_ID); } @Override diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 514e7b22d8..f444bd5718 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -36,7 +36,6 @@ import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; -import com.android.launcher3.LauncherWidgetHolder; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.FileLog; @@ -49,6 +48,7 @@ import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.Executors; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LooperExecutor; +import com.android.launcher3.widget.LauncherWidgetHolder; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 48b3acfbf1..95ac7b0ca9 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -47,6 +47,7 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.LogConfig; +import com.android.launcher3.widget.LauncherWidgetHolder; import java.io.InvalidObjectException; import java.util.Arrays; @@ -353,9 +354,12 @@ public class RestoreDbTask { private void restoreAppWidgetIdsIfExists(Context context) { SharedPreferences prefs = Utilities.getPrefs(context); if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) { + LauncherWidgetHolder holder = new LauncherWidgetHolder(context); AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(), - IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray()); + IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray(), + holder); + holder.destroy(); } else { FileLog.d(TAG, "No app widget ids to restore."); } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java index fff8fbb12a..9c21ea2bb7 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java @@ -16,301 +16,87 @@ package com.android.launcher3.widget; -import static android.app.Activity.RESULT_CANCELED; +import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; -import android.app.PendingIntent; import android.appwidget.AppWidgetHost; -import android.appwidget.AppWidgetHostView; -import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; -import android.content.ActivityNotFoundException; import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.util.SparseArray; -import android.view.View; -import android.widget.RemoteViews; -import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.BaseActivity; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.model.WidgetsModel; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.shared.TestProtocol; -import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.ArrayList; 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. */ -public class LauncherAppWidgetHost extends AppWidgetHost { - - private static final int FLAG_LISTENING = 1; - private static final int FLAG_STATE_IS_NORMAL = 1 << 1; - private static final int FLAG_ACTIVITY_STARTED = 1 << 2; - private 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; - - public static final int APPWIDGET_HOST_ID = 1024; - - private final ArrayList mProviderChangeListeners = new ArrayList<>(); - private final SparseArray mViews = new SparseArray<>(); - private final SparseArray mPendingViews = new SparseArray<>(); - private final SparseArray mDeferredViews = new SparseArray<>(); - private final SparseArray mCachedRemoteViews = new SparseArray<>(); +class LauncherAppWidgetHost extends AppWidgetHost { + @NonNull + private final ArrayList + mProviderChangeListeners = new ArrayList<>(); + @NonNull private final Context mContext; - private int mFlags = FLAG_STATE_IS_NORMAL; - private IntConsumer mAppWidgetRemovedCallback = null; + @Nullable + private final IntConsumer mAppWidgetRemovedCallback; - /** - * This serves for the purpose of getting rid of the hidden API calling of InteractionHandler - */ - public interface LauncherWidgetInteractionHandler { - /** - * Invoked when the user performs an interaction on the View. - * - * @param view the View with which the user interacted - * @param pendingIntent the base PendingIntent associated with the view - * @param response the response to the interaction, which knows how to fill in the - * attached PendingIntent - */ - boolean onInteraction( - View view, - PendingIntent pendingIntent, - RemoteViews.RemoteResponse response); - } + @NonNull + private final LauncherWidgetHolder mHolder; - public LauncherAppWidgetHost(Context context) { - this(context, null); - } - - public LauncherAppWidgetHost(Context context, - IntConsumer appWidgetRemovedCallback) { + public LauncherAppWidgetHost(@NonNull Context context, + @Nullable IntConsumer appWidgetRemovedCallback, @NonNull LauncherWidgetHolder holder) { super(context, APPWIDGET_HOST_ID); mContext = context; mAppWidgetRemovedCallback = appWidgetRemovedCallback; - } - - @Override - protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId, - AppWidgetProviderInfo appWidget) { - final LauncherAppWidgetHostView view; - if (mPendingViews.get(appWidgetId) != null) { - view = mPendingViews.get(appWidgetId); - mPendingViews.remove(appWidgetId); - } else if (mDeferredViews.get(appWidgetId) != null) { - // In case the widget view is deferred, we will simply return the deferred view as - // opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher - // already added the former to the workspace. - view = mDeferredViews.get(appWidgetId); - } else { - view = new LauncherAppWidgetHostView(context); - } - mViews.put(appWidgetId, view); - return view; - } - - @Override - public void startListening() { - if (WidgetsModel.GO_DISABLE_WIDGETS) { - return; - } - mFlags |= FLAG_LISTENING; - try { - super.startListening(); - } catch (Exception e) { - if (!Utilities.isBinderSizeError(e)) { - throw new RuntimeException(e); - } - // We're willing to let this slide. The exception is being caused by the list of - // RemoteViews which is being passed back. The startListening relationship will - // have been established by this point, and we will end up populating the - // widgets upon bind anyway. See issue 14255011 for more context. - } - - // We go in reverse order and inflate any deferred or cached widget - for (int i = mViews.size() - 1; i >= 0; i--) { - LauncherAppWidgetHostView view = mViews.valueAt(i); - if (view instanceof DeferredAppWidgetHostView) { - view.reInflate(); - } - if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) { - final int appWidgetId = mViews.keyAt(i); - if (view == mDeferredViews.get(appWidgetId)) { - // If the widget view was deferred, we'll need to call super.createView here - // to make the binder call to system process to fetch cumulative updates to this - // widget, as well as setting up this view for future updates. - super.createView(view.mLauncher, appWidgetId, view.getAppWidgetInfo()); - // At this point #onCreateView should have been called, which in turn returned - // the deferred view. There's no reason to keep the reference anymore, so we - // removed it here. - mDeferredViews.remove(appWidgetId); - } - } - } - } - - @Override - public void stopListening() { - if (WidgetsModel.GO_DISABLE_WIDGETS) { - return; - } - mFlags &= ~FLAG_LISTENING; - super.stopListening(); - } - - public boolean isListening() { - return (mFlags & FLAG_LISTENING) != 0; + mHolder = holder; } /** - * Sets or unsets a flag the can change whether the widget host should be in the listening - * state. + * Add a listener that is triggered when the providers of the widgets are changed + * @param listener The listener that notifies when the providers changed */ - private void setShouldListenFlag(int flag, boolean on) { - if (on) { - mFlags |= flag; - } else { - mFlags &= ~flag; - } - - final boolean listening = isListening(); - if (!listening && (mFlags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN) { - // Postpone starting listening until all flags are on. - startListening(); - } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) { - // Postpone stopping listening until the activity is stopped. - stopListening(); - } + public void addProviderChangeListener( + @NonNull LauncherWidgetHolder.ProviderChangedListener listener) { + mProviderChangeListeners.add(listener); } /** - * Registers an "entering/leaving Normal state" event. + * Remove the specified listener from the host + * @param listener The listener that is to be removed from the host */ - public void setStateIsNormal(boolean isNormal) { - setShouldListenFlag(FLAG_STATE_IS_NORMAL, isNormal); - } - - /** - * Registers an "activity started/stopped" event. - */ - public void setActivityStarted(boolean isStarted) { - setShouldListenFlag(FLAG_ACTIVITY_STARTED, isStarted); - } - - /** - * Registers an "activity paused/resumed" event. - */ - public void setActivityResumed(boolean isResumed) { - setShouldListenFlag(FLAG_ACTIVITY_RESUMED, isResumed); + public void removeProviderChangeListener( + LauncherWidgetHolder.ProviderChangedListener listener) { + mProviderChangeListeners.remove(listener); } @Override - public int allocateAppWidgetId() { - if (WidgetsModel.GO_DISABLE_WIDGETS) { - return AppWidgetManager.INVALID_APPWIDGET_ID; - } - - return super.allocateAppWidgetId(); - } - - public void addProviderChangeListener(ProviderChangedListener callback) { - mProviderChangeListeners.add(callback); - } - - public void removeProviderChangeListener(ProviderChangedListener callback) { - mProviderChangeListeners.remove(callback); - } - protected void onProvidersChanged() { if (!mProviderChangeListeners.isEmpty()) { - for (ProviderChangedListener callback : new ArrayList<>(mProviderChangeListeners)) { + for (LauncherWidgetHolder.ProviderChangedListener callback : + new ArrayList<>(mProviderChangeListeners)) { callback.notifyWidgetProvidersChanged(); } } } - public void addPendingView(int appWidgetId, PendingAppWidgetHostView view) { - mPendingViews.put(appWidgetId, view); - } - - public AppWidgetHostView createView(Context context, int appWidgetId, - LauncherAppWidgetProviderInfo appWidget) { - if (appWidget.isCustomWidget()) { - LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context); - lahv.setAppWidget(0, appWidget); - CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv); - return lahv; - } else if ((mFlags & FLAG_LISTENING) == 0) { - // Since the launcher hasn't started listening to widget updates, we can't simply call - // super.createView here because the later will make a binder call to retrieve - // RemoteViews from system process. - // TODO: have launcher always listens to widget updates in background so that this - // check can be removed altogether. - if (FeatureFlags.ENABLE_CACHED_WIDGET.get() - && mCachedRemoteViews.get(appWidgetId) != null) { - // We've found RemoteViews from cache for this widget, so we will instantiate a - // widget host view and populate it with the cached RemoteViews. - final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context); - view.setAppWidget(appWidgetId, appWidget); - view.updateAppWidget(mCachedRemoteViews.get(appWidgetId)); - mDeferredViews.put(appWidgetId, view); - mViews.put(appWidgetId, view); - return view; - } else { - // When cache misses, a placeholder for the widget will be returned instead. - DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context); - view.setAppWidget(appWidgetId, appWidget); - mViews.put(appWidgetId, view); - return view; - } - } else { - try { - return super.createView(context, appWidgetId, appWidget); - } catch (Exception e) { - if (!Utilities.isBinderSizeError(e)) { - throw new RuntimeException(e); - } - - // If the exception was thrown while fetching the remote views, let the view stay. - // This will ensure that if the widget posts a valid update later, the view - // will update. - LauncherAppWidgetHostView view = mViews.get(appWidgetId); - if (view == null) { - view = onCreateView(mContext, appWidgetId, appWidget); - } - view.setAppWidget(appWidgetId, appWidget); - view.switchToErrorView(); - return view; - } - } + @Override + @NonNull + public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId, + AppWidgetProviderInfo appWidget) { + return mHolder.onCreateView(context, appWidgetId, appWidget); } /** * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. */ @Override - protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { + protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) { LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo( mContext, appWidget); super.onProviderChanged(appWidgetId, info); @@ -324,6 +110,7 @@ public class LauncherAppWidgetHost extends AppWidgetHost { * * @param appWidgetId TODO: make this override when SDK is updated */ + @Override public void onAppWidgetRemoved(int appWidgetId) { if (mAppWidgetRemovedCallback == null) { return; @@ -331,92 +118,12 @@ public class LauncherAppWidgetHost extends AppWidgetHost { mAppWidgetRemovedCallback.accept(appWidgetId); } - @Override - public void deleteAppWidgetId(int appWidgetId) { - super.deleteAppWidgetId(appWidgetId); - mViews.remove(appWidgetId); - } - + /** + * The same as super.clearViews(), except with the scope exposed + */ @Override public void clearViews() { super.clearViews(); - if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) { - // First, we clear any previously cached content from existing widgets - mCachedRemoteViews.clear(); - mDeferredViews.clear(); - // Then we proceed to cache the content from the widgets - for (int i = 0; i < mViews.size(); i++) { - final int appWidgetId = mViews.keyAt(i); - final LauncherAppWidgetHostView view = mViews.get(appWidgetId); - mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews); - } - } - mViews.clear(); } - public void startBindFlow(BaseActivity activity, - int appWidgetId, AppWidgetProviderInfo info, int requestCode) { - - if (WidgetsModel.GO_DISABLE_WIDGETS) { - sendActionCancelled(activity, requestCode); - return; - } - - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND) - .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) - .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider) - .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, info.getProfile()); - // TODO: we need to make sure that this accounts for the options bundle. - // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options); - activity.startActivityForResult(intent, requestCode); - } - - /** - * Launches an app widget's configuration activity. - * @param activity The activity from which to launch the configuration activity - * @param widgetId The id of the bound app widget to be configured - * @param requestCode An optional request code to be returned with the result - */ - public void startConfigActivity(BaseDraggingActivity activity, int widgetId, int requestCode) { - if (WidgetsModel.GO_DISABLE_WIDGETS) { - sendActionCancelled(activity, requestCode); - return; - } - - try { - TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startConfigActivity"); - startAppWidgetConfigureActivityForResult(activity, widgetId, 0, requestCode, - getConfigurationActivityOptions(activity, widgetId)); - } catch (ActivityNotFoundException | SecurityException e) { - Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - sendActionCancelled(activity, requestCode); - } - } - - /** - * Returns an {@link android.app.ActivityOptions} bundle from the {code activity} for launching - * the configuration of the {@code widgetId} app widget, or null of options cannot be produced. - */ - @Nullable - private Bundle getConfigurationActivityOptions(BaseDraggingActivity activity, int widgetId) { - LauncherAppWidgetHostView view = mViews.get(widgetId); - if (view == null) return null; - Object tag = view.getTag(); - if (!(tag instanceof ItemInfo)) return null; - Bundle bundle = activity.getActivityLaunchOptions(view, (ItemInfo) tag).toBundle(); - bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY); - return bundle; - } - - private void sendActionCancelled(final BaseActivity activity, final int requestCode) { - new Handler().post(() -> activity.onActivityResult(requestCode, RESULT_CANCELED, null)); - } - - /** - * Listener for getting notifications on provider changes. - */ - public interface ProviderChangedListener { - - void notifyWidgetProvidersChanged(); - } } diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java new file mode 100644 index 0000000000..54977297b8 --- /dev/null +++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java @@ -0,0 +1,508 @@ +/** + * Copyright (C) 2022 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 static android.app.Activity.RESULT_CANCELED; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.SparseArray; +import android.view.View; +import android.widget.RemoteViews; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.launcher3.BaseActivity; +import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.model.WidgetsModel; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.testing.TestLogging; +import com.android.launcher3.testing.shared.TestProtocol; +import com.android.launcher3.uioverrides.ApiWrapper; +import com.android.launcher3.widget.custom.CustomWidgetManager; + +import java.util.function.IntConsumer; + +/** + * A wrapper for LauncherAppWidgetHost. This class is created so the AppWidgetHost could run in + * background. + */ +public class LauncherWidgetHolder { + public static final int APPWIDGET_HOST_ID = 1024; + + private static final int FLAG_LISTENING = 1; + private static final int FLAG_STATE_IS_NORMAL = 1 << 1; + private static final int FLAG_ACTIVITY_STARTED = 1 << 2; + private 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; + + @NonNull + private final Context mContext; + + @NonNull + private final AppWidgetHost mWidgetHost; + + @NonNull + private final SparseArray mViews = new SparseArray<>(); + @NonNull + private final SparseArray mPendingViews = new SparseArray<>(); + @NonNull + private final SparseArray mDeferredViews = new SparseArray<>(); + @NonNull + private final SparseArray mCachedRemoteViews = new SparseArray<>(); + + private int mFlags = 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; + + public LauncherWidgetHolder(@NonNull Context context) { + this(context, null); + } + + public LauncherWidgetHolder(@NonNull Context context, + @Nullable IntConsumer appWidgetRemovedCallback) { + mContext = context; + mWidgetHost = createHost(context, appWidgetRemovedCallback); + } + + protected AppWidgetHost createHost( + Context context, @Nullable IntConsumer appWidgetRemovedCallback) { + return new LauncherAppWidgetHost(context, appWidgetRemovedCallback, this); + } + + /** + * Starts listening to the widget updates from the server side + */ + public void startListening() { + if (WidgetsModel.GO_DISABLE_WIDGETS) { + return; + } + setListeningFlag(true); + try { + mWidgetHost.startListening(); + } catch (Exception e) { + if (!Utilities.isBinderSizeError(e)) { + throw new RuntimeException(e); + } + // We're willing to let this slide. The exception is being caused by the list of + // RemoteViews which is being passed back. The startListening relationship will + // have been established by this point, and we will end up populating the + // widgets upon bind anyway. See issue 14255011 for more context. + } + + // We go in reverse order and inflate any deferred or cached widget + for (int i = mViews.size() - 1; i >= 0; i--) { + LauncherAppWidgetHostView view = mViews.valueAt(i); + if (view instanceof DeferredAppWidgetHostView) { + view.reInflate(); + } + if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) { + final int appWidgetId = mViews.keyAt(i); + if (view == mDeferredViews.get(appWidgetId)) { + // If the widget view was deferred, we'll need to call super.createView here + // to make the binder call to system process to fetch cumulative updates to this + // widget, as well as setting up this view for future updates. + mWidgetHost.createView(view.mLauncher, appWidgetId, + view.getAppWidgetInfo()); + // At this point #onCreateView should have been called, which in turn returned + // the deferred view. There's no reason to keep the reference anymore, so we + // removed it here. + mDeferredViews.remove(appWidgetId); + } + } + } + } + + /** + * Registers an "activity started/stopped" event. + */ + public void setActivityStarted(boolean isStarted) { + setShouldListenFlag(FLAG_ACTIVITY_STARTED, isStarted); + } + + /** + * Registers an "activity paused/resumed" event. + */ + public void setActivityResumed(boolean isResumed) { + setShouldListenFlag(FLAG_ACTIVITY_RESUMED, isResumed); + } + + /** + * Set the NORMAL state of the widget host + * @param isNormal True if setting the host to be in normal state, false otherwise + */ + public void setStateIsNormal(boolean isNormal) { + setShouldListenFlag(FLAG_STATE_IS_NORMAL, isNormal); + } + + /** + * Delete the specified app widget from the host + * @param appWidgetId The ID of the app widget to be deleted + */ + public void deleteAppWidgetId(int appWidgetId) { + mWidgetHost.deleteAppWidgetId(appWidgetId); + 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 + */ + public void destroy() { + // No-op + } + + /** + * @return The allocated app widget id if allocation is successful, returns -1 otherwise + */ + public int allocateAppWidgetId() { + if (WidgetsModel.GO_DISABLE_WIDGETS) { + return AppWidgetManager.INVALID_APPWIDGET_ID; + } + + return mWidgetHost.allocateAppWidgetId(); + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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 + * @param requestCode The request code + */ + public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId, + int requestCode) { + if (WidgetsModel.GO_DISABLE_WIDGETS) { + sendActionCancelled(activity, requestCode); + return; + } + + try { + TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startConfigActivity"); + mWidgetHost.startAppWidgetConfigureActivityForResult(activity, widgetId, 0, requestCode, + getConfigurationActivityOptions(activity, widgetId)); + } catch (ActivityNotFoundException | SecurityException e) { + Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + sendActionCancelled(activity, requestCode); + } + } + + private void sendActionCancelled(final BaseActivity activity, final int requestCode) { + MAIN_EXECUTOR.execute( + () -> activity.onActivityResult(requestCode, RESULT_CANCELED, null)); + } + + /** + * Returns an {@link android.app.ActivityOptions} bundle from the {code activity} for launching + * the configuration of the {@code widgetId} app widget, or null of options cannot be produced. + */ + @Nullable + protected Bundle getConfigurationActivityOptions(@NonNull BaseDraggingActivity activity, + int widgetId) { + LauncherAppWidgetHostView view = mViews.get(widgetId); + if (view == null) return null; + Object tag = view.getTag(); + if (!(tag instanceof ItemInfo)) return null; + Bundle bundle = activity.getActivityLaunchOptions(view, (ItemInfo) tag).toBundle(); + bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY); + return bundle; + } + + /** + * Starts the binding flow for the widget + * @param activity The activity for which to bind the widget + * @param appWidgetId The ID of the widget + * @param info The {@link AppWidgetProviderInfo} of the widget + * @param requestCode The request code + */ + public void startBindFlow(@NonNull BaseActivity activity, + int appWidgetId, @NonNull AppWidgetProviderInfo info, int requestCode) { + if (WidgetsModel.GO_DISABLE_WIDGETS) { + sendActionCancelled(activity, requestCode); + return; + } + + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND) + .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider) + .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, info.getProfile()); + // TODO: we need to make sure that this accounts for the options bundle. + // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options); + activity.startActivityForResult(intent, requestCode); + } + + /** + * Stop the host from listening to the widget updates + */ + public void stopListening() { + if (WidgetsModel.GO_DISABLE_WIDGETS) { + return; + } + + mWidgetHost.stopListening(); + setListeningFlag(false); + } + + protected void setListeningFlag(final boolean isListening) { + if (isListening) { + mFlags |= FLAG_LISTENING; + return; + } + mFlags &= ~FLAG_LISTENING; + } + + /** + * Set the interaction handler for the widget host + * @param handler The interaction handler + */ + public void setInteractionHandler( + @Nullable LauncherWidgetInteractionHandler handler) { + ApiWrapper.setHostInteractionHandler(mWidgetHost, handler); + } + + /** + * Delete the host + */ + public void deleteHost() { + mWidgetHost.deleteHost(); + } + + /** + * @return The app widget ids + */ + @NonNull + public int[] getAppWidgetIds() { + return mWidgetHost.getAppWidgetIds(); + } + + /** + * 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) { + if (appWidget.isCustomWidget()) { + LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context); + lahv.setAppWidget(0, appWidget); + CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv); + return lahv; + } else if ((mFlags & FLAG_LISTENING) == 0) { + // Since the launcher hasn't started listening to widget updates, we can't simply call + // super.createView here because the later will make a binder call to retrieve + // RemoteViews from system process. + // TODO: have launcher always listens to widget updates in background so that this + // check can be removed altogether. + if (FeatureFlags.ENABLE_CACHED_WIDGET.get() + && mCachedRemoteViews.get(appWidgetId) != null) { + // We've found RemoteViews from cache for this widget, so we will instantiate a + // widget host view and populate it with the cached RemoteViews. + final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context); + view.setAppWidget(appWidgetId, appWidget); + view.updateAppWidget(mCachedRemoteViews.get(appWidgetId)); + mDeferredViews.put(appWidgetId, view); + mViews.put(appWidgetId, view); + return view; + } else { + // When cache misses, a placeholder for the widget will be returned instead. + DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context); + view.setAppWidget(appWidgetId, appWidget); + mViews.put(appWidgetId, view); + return view; + } + } else { + try { + return mWidgetHost.createView(context, appWidgetId, appWidget); + } catch (Exception e) { + if (!Utilities.isBinderSizeError(e)) { + throw new RuntimeException(e); + } + + // If the exception was thrown while fetching the remote views, let the view stay. + // This will ensure that if the widget posts a valid update later, the view + // will update. + LauncherAppWidgetHostView view = mViews.get(appWidgetId); + if (view == null) { + view = onCreateView(mContext, appWidgetId, appWidget); + } + view.setAppWidget(appWidgetId, appWidget); + view.switchToErrorView(); + return view; + } + } + } + + /** + * Listener for getting notifications on provider changes. + */ + public interface ProviderChangedListener { + /** + * Notify the listener that the providers have changed + */ + 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 + * @param appWidget The provider info of the added widget + * @return A view for the specified app widget + */ + @NonNull + public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId, + AppWidgetProviderInfo appWidget) { + final LauncherAppWidgetHostView view; + if (getPendingView(appWidgetId) != null) { + view = getPendingView(appWidgetId); + removePendingView(appWidgetId); + } else if (mDeferredViews.get(appWidgetId) != null) { + // In case the widget view is deferred, we will simply return the deferred view as + // opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher + // already added the former to the workspace. + view = mDeferredViews.get(appWidgetId); + } else { + view = new LauncherAppWidgetHostView(context); + } + mViews.put(appWidgetId, view); + return view; + } + + /** + * Clears all the views from the host + */ + public void clearViews() { + LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost; + tempHost.clearViews(); + if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) { + // First, we clear any previously cached content from existing widgets + mCachedRemoteViews.clear(); + mDeferredViews.clear(); + // Then we proceed to cache the content from the widgets + for (int i = 0; i < mViews.size(); i++) { + final int appWidgetId = mViews.keyAt(i); + final LauncherAppWidgetHostView view = mViews.get(appWidgetId); + mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews); + } + } + mViews.clear(); + } + + /** + * @return True if the host is listening to the updates, false otherwise + */ + public boolean isListening() { + return (mFlags & FLAG_LISTENING) != 0; + } + + /** + * Sets or unsets a flag the can change whether the widget host should be in the listening + * state. + */ + private void setShouldListenFlag(int flag, boolean on) { + if (on) { + mFlags |= flag; + } else { + mFlags &= ~flag; + } + + final boolean listening = isListening(); + if (!listening && (mFlags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN) { + // Postpone starting listening until all flags are on. + startListening(); + } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) { + // Postpone stopping listening until the activity is stopped. + stopListening(); + } + } + + /** + * Set as a substitution for the hidden interaction handler in RemoteViews + */ + public interface LauncherWidgetInteractionHandler { + /** + * Invoked when the user performs an interaction on the View. + * + * @param view the View with which the user interacted + * @param pendingIntent the base PendingIntent associated with the view + * @param response the response to the interaction, which knows how to fill in the + * attached PendingIntent + */ + boolean onInteraction( + View view, + PendingIntent pendingIntent, + RemoteViews.RemoteResponse response); + } +} diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index 21b2647d0c..72ec62979e 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -65,7 +65,7 @@ import com.android.launcher3.views.SpringRelativeLayout; import com.android.launcher3.views.StickyHeaderLayout; import com.android.launcher3.views.WidgetsEduView; import com.android.launcher3.widget.BaseWidgetSheet; -import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener; +import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.picker.search.SearchModeListener; import com.android.launcher3.widget.picker.search.WidgetsSearchBar; diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java index ea0f5a3c5d..02f4ecee3c 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java @@ -24,7 +24,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.Utilities; -import com.android.launcher3.widget.LauncherAppWidgetHost; +import com.android.launcher3.widget.LauncherWidgetHolder; /** * A wrapper for the hidden API calls @@ -43,7 +43,7 @@ public class ApiWrapper { * @param handler InteractionHandler for the views in the host */ public static void setHostInteractionHandler(@NonNull AppWidgetHost host, - @Nullable LauncherAppWidgetHost.LauncherWidgetInteractionHandler handler) { + @Nullable LauncherWidgetHolder.LauncherWidgetInteractionHandler handler) { // No-op } } diff --git a/tests/src/com/android/launcher3/util/WidgetUtils.java b/tests/src/com/android/launcher3/util/WidgetUtils.java index 6fc84914f8..e514142f7d 100644 --- a/tests/src/com/android/launcher3/util/WidgetUtils.java +++ b/tests/src/com/android/launcher3/util/WidgetUtils.java @@ -20,7 +20,6 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; -import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; @@ -32,8 +31,8 @@ import android.os.Process; import com.android.launcher3.LauncherSettings; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; -import com.android.launcher3.widget.LauncherAppWidgetHost; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.widget.LauncherWidgetHolder; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetManagerHelper; @@ -71,14 +70,19 @@ public class WidgetUtils { pendingInfo.minSpanY = item.minSpanY; Bundle options = pendingInfo.getDefaultSizeOptions(targetContext); - AppWidgetHost host = new LauncherAppWidgetHost(targetContext); - int widgetId = host.allocateAppWidgetId(); - if (!new WidgetManagerHelper(targetContext) - .bindAppWidgetIdIfAllowed(widgetId, info, options)) { - host.deleteAppWidgetId(widgetId); - throw new IllegalArgumentException("Unable to bind widget id"); + LauncherWidgetHolder holder = new LauncherWidgetHolder(targetContext); + try { + int widgetId = holder.allocateAppWidgetId(); + if (!new WidgetManagerHelper(targetContext) + .bindAppWidgetIdIfAllowed(widgetId, info, options)) { + holder.deleteAppWidgetId(widgetId); + throw new IllegalArgumentException("Unable to bind widget id"); + } + item.appWidgetId = widgetId; + } finally { + // Necessary to destroy the holder to free up possible activity context + holder.destroy(); } - item.appWidgetId = widgetId; } return item; }