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