Merge changes If022ec8d,I34341937 into tm-qpr-dev
* changes:
Move LauncherWidgetHolder to widget package
Move most of the functions in LauncherAppWidgetHost to LauncherWidgetHolder
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 729eea9..379a6cd 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.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 @@
* @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 2dde6b6..353d817 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.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 9813c8a..f90feb8 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.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.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 75e89b2..eb6d096 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -2,7 +2,6 @@
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.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.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 @@
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 @@
* 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 @@
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 @@
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 64666b0..efdd5e1 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.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 @@
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<String, Resources> customizationApkInfo = PackageManagerHelper.findSystemApk(
ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager());
@@ -109,7 +109,7 @@
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 @@
@Thunk
final Context mContext;
@Thunk
- final AppWidgetHost mAppWidgetHost;
+ final LauncherWidgetHolder mAppWidgetHolder;
protected final LayoutParserCallback mCallback;
protected final PackageManager mPackageManager;
@@ -174,17 +174,17 @@
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<XmlPullParser> 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 4daca8b..af13bea 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.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 @@
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 @@
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 @@
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 8453f2c..578efdf 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -210,6 +210,7 @@
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 @@
} 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 58e85fe..457e126 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -22,7 +22,6 @@
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.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.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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
*
* @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 @@
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 @@
.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 @@
defaultLayout = idp.demoModeLayoutId;
}
- return new DefaultLayoutParser(getContext(), widgetHost,
+ return new DefaultLayoutParser(getContext(), widgetHolder,
mOpenHelper, getContext().getResources(), defaultLayout);
}
@@ -932,40 +938,46 @@
*/
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 @@
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 5fcd46f..0000000
--- 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 a8def69..517bbf8 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -107,8 +107,9 @@
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 05b1984..5a49f4a 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.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.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 @@
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 @@
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 @@
return;
}
- mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId();
+ mPendingBindWidgetId = mAppWidgetHolder.allocateAppWidgetId();
AppWidgetProviderInfo widgetProviderInfo = mRequest.getAppWidgetProviderInfo(this);
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
mPendingBindWidgetId, widgetProviderInfo, mWidgetOptions);
@@ -349,7 +352,7 @@
}
// request bind widget
- mAppWidgetHost.startBindFlow(this, mPendingBindWidgetId,
+ mAppWidgetHolder.startBindFlow(this, mPendingBindWidgetId,
mRequest.getAppWidgetProviderInfo(this), REQUEST_BIND_APPWIDGET);
}
@@ -363,6 +366,15 @@
}
@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);
mSlideInView.close(/* animate= */ true);
@@ -378,7 +390,7 @@
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 482e923..2361907 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.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 @@
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 514e7b2..f444bd5 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.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.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 48b3acf..95ac7b0 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.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 @@
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 fff8fbb..9c21ea2 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 {
+class LauncherAppWidgetHost extends AppWidgetHost {
+ @NonNull
+ private final ArrayList<LauncherWidgetHolder.ProviderChangedListener>
+ mProviderChangeListeners = new ArrayList<>();
- 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<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
- private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
- private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
- private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
- private final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
-
+ @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 @@
*
* @param appWidgetId TODO: make this override when SDK is updated
*/
+ @Override
public void onAppWidgetRemoved(int appWidgetId) {
if (mAppWidgetRemovedCallback == null) {
return;
@@ -331,92 +118,12 @@
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 0000000..5497729
--- /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<LauncherAppWidgetHostView> mViews = new SparseArray<>();
+ @NonNull
+ private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
+ @NonNull
+ private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
+ @NonNull
+ private final SparseArray<RemoteViews> 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 21b2647..72ec629 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.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 ea0f5a3..02f4ece 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.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 @@
* @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 6fc8491..e514142 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 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 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 @@
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;
}