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:
Sihua Ma
2022-11-17 01:46:55 +00:00
committed by Android (Google) Code Review
19 changed files with 682 additions and 615 deletions

View File

@@ -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);
}
}

View File

@@ -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";

View File

@@ -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;

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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.");
}

View File

@@ -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();
}
}

View 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);
}
}

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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;
}