Move most of the functions in LauncherAppWidgetHost to LauncherWidgetHolder
Test: N/A
Bug: 235358918
Change-Id: I343419376491203a195154f2766b12e5def38879
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 75e89b2..8604f1a 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,6 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.ContentWriter;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
@@ -32,7 +31,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 +49,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 +62,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 +99,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..0003510 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;
@@ -74,7 +73,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 +108,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 +155,7 @@
@Thunk
final Context mContext;
@Thunk
- final AppWidgetHost mAppWidgetHost;
+ final LauncherWidgetHolder mAppWidgetHolder;
protected final LayoutParserCallback mCallback;
protected final PackageManager mPackageManager;
@@ -174,17 +173,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..d8558f0 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;
@@ -55,9 +54,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 +335,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 +348,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 ab7f622..de2ef55 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1720,6 +1720,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..a6731a5 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,6 @@
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 org.xmlpull.v1.XmlPullParser;
@@ -255,17 +255,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 +536,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 +548,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 +556,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 +568,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 +580,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 +607,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 +626,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 +636,7 @@
defaultLayout = idp.demoModeLayoutId;
}
- return new DefaultLayoutParser(getContext(), widgetHost,
+ return new DefaultLayoutParser(getContext(), widgetHolder,
mOpenHelper, getContext().getResources(), defaultLayout);
}
@@ -932,40 +937,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 +1077,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
index 5fcd46f..2612b9c 100644
--- a/src/com/android/launcher3/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/LauncherWidgetHolder.java
@@ -15,17 +15,39 @@
*/
package com.android.launcher3;
+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.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.DeferredAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.function.IntConsumer;
@@ -34,8 +56,36 @@
* 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 LauncherAppWidgetHost mWidgetHost;
+ 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);
@@ -43,30 +93,70 @@
public LauncherWidgetHolder(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback) {
- mWidgetHost = new LauncherAppWidgetHost(context, 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() {
- mWidgetHost.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.getLauncher(), 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);
+ }
+ }
+ }
}
/**
- * Set the STARTED state of the widget host
- * @param isStarted True if setting the host as started, false otherwise
+ * Registers an "activity started/stopped" event.
*/
public void setActivityStarted(boolean isStarted) {
- mWidgetHost.setActivityStarted(isStarted);
+ setShouldListenFlag(FLAG_ACTIVITY_STARTED, isStarted);
}
/**
- * Set the RESUMED state of the widget host
- * @param isResumed True if setting the host as resumed, false otherwise
+ * Registers an "activity paused/resumed" event.
*/
public void setActivityResumed(boolean isResumed) {
- mWidgetHost.setActivityResumed(isResumed);
+ setShouldListenFlag(FLAG_ACTIVITY_RESUMED, isResumed);
}
/**
@@ -74,7 +164,7 @@
* @param isNormal True if setting the host to be in normal state, false otherwise
*/
public void setStateIsNormal(boolean isNormal) {
- mWidgetHost.setStateIsNormal(isNormal);
+ setShouldListenFlag(FLAG_STATE_IS_NORMAL, isNormal);
}
/**
@@ -83,6 +173,7 @@
*/
public void deleteAppWidgetId(int appWidgetId) {
mWidgetHost.deleteAppWidgetId(appWidgetId);
+ mViews.remove(appWidgetId);
}
/**
@@ -91,20 +182,37 @@
* @param view The {@link PendingAppWidgetHostView} of the app widget
*/
public void addPendingView(int appWidgetId, @NonNull PendingAppWidgetHostView view) {
- mWidgetHost.addPendingView(appWidgetId, view);
+ mPendingViews.put(appWidgetId, view);
}
/**
- * @return True if the host is listening to the widget updates, false otherwise
+ * @param appWidgetId The app widget id of the specified widget
+ * @return The {@link PendingAppWidgetHostView} of the widget if it exists, null otherwise
*/
- public boolean isListening() {
- return mWidgetHost.isListening();
+ @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();
}
@@ -112,18 +220,18 @@
* 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);
+ 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(
- LauncherAppWidgetHost.ProviderChangedListener listener) {
- mWidgetHost.removeProviderChangeListener(listener);
+ public void removeProviderChangeListener(ProviderChangedListener listener) {
+ LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
+ tempHost.removeProviderChangeListener(listener);
}
/**
@@ -134,7 +242,40 @@
*/
public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId,
int requestCode) {
- mWidgetHost.startConfigActivity(activity, widgetId, 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;
}
/**
@@ -146,27 +287,38 @@
*/
public void startBindFlow(@NonNull BaseActivity activity,
int appWidgetId, @NonNull AppWidgetProviderInfo info, int requestCode) {
- mWidgetHost.startBindFlow(activity, appWidgetId, info, 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);
}
- /**
- * 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);
+ protected void setListeningFlag(final boolean isListening) {
+ if (isListening) {
+ mFlags |= FLAG_LISTENING;
+ return;
+ }
+ mFlags &= ~FLAG_LISTENING;
}
/**
@@ -174,14 +326,184 @@
* @param handler The interaction handler
*/
public void setInteractionHandler(
- @Nullable LauncherAppWidgetHost.LauncherWidgetInteractionHandler handler) {
+ @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() {
- mWidgetHost.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.getLastRemoteViews());
+ }
+ }
+ 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/Workspace.java b/src/com/android/launcher3/Workspace.java
index 27e1ba1..39679a9 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -63,6 +63,7 @@
import androidx.annotation.Nullable;
+import com.android.launcher3.LauncherWidgetHolder.ProviderChangedListener;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.Interpolators;
@@ -107,7 +108,6 @@
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.NavigableAppWidgetHostView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 05b1984..4fb5ff6 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -53,10 +53,13 @@
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;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherWidgetHolder;
import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.ItemInstallQueue;
@@ -69,7 +72,6 @@
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.NavigableAppWidgetHostView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -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..0646e7f 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -68,6 +68,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherWidgetHolder;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
@@ -99,7 +100,6 @@
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.LocalColorExtractor;
import com.android.launcher3.widget.NavigableAppWidgetHostView;
@@ -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/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 48b3acf..70dd98e 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -38,6 +38,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherWidgetHolder;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DeviceGridState;
@@ -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..c57139d 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -16,301 +16,88 @@
package com.android.launcher3.widget;
-import static android.app.Activity.RESULT_CANCELED;
+import static com.android.launcher3.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 com.android.launcher3.LauncherWidgetHolder;
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 {
+ @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 +111,7 @@
*
* @param appWidgetId TODO: make this override when SDK is updated
*/
+ @Override
public void onAppWidgetRemoved(int appWidgetId) {
if (mAppWidgetRemovedCallback == null) {
return;
@@ -331,92 +119,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/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index bc3889f..990282b 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -37,6 +37,7 @@
import android.widget.Advanceable;
import android.widget.RemoteViews;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.CheckLongPressHelper;
@@ -172,6 +173,16 @@
mReinflateOnConfigChange = !isSameOrientation();
}
+ @NonNull
+ public Launcher getLauncher() {
+ return mLauncher;
+ }
+
+ @Nullable
+ public RemoteViews getLastRemoteViews() {
+ return mLastRemoteViews;
+ }
+
private boolean isSameOrientation() {
return mLauncher.getResources().getConfiguration().orientation ==
mLauncher.getOrientation();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 21b2647..ee8d8e8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -52,6 +52,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherWidgetHolder.ProviderChangedListener;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
@@ -65,7 +66,6 @@
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.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.search.SearchModeListener;
import com.android.launcher3.widget.picker.search.WidgetsSearchBar;