Merge "Moving widget inflation logic to a separate class" into main

This commit is contained in:
Sunny Goyal
2024-01-10 20:03:15 +00:00
committed by Android (Google) Code Review
5 changed files with 229 additions and 156 deletions
@@ -39,7 +39,6 @@ import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.OnColorHintListener;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.WallpaperColorHints;
import com.android.launcher3.util.WindowBounds;
@@ -55,16 +54,12 @@ public abstract class BaseDraggingActivity extends BaseActivity
public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
private ActionMode mCurrentActionMode;
protected boolean mIsSafeModeEnabled;
private int mThemeRes = R.style.AppTheme;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
DisplayController.INSTANCE.get(this).addChangeListener(this);
// Update theme
@@ -183,6 +178,6 @@ public abstract class BaseDraggingActivity extends BaseActivity
@Override
public boolean isAppBlockedForSafeMode() {
return mIsSafeModeEnabled;
return LauncherAppState.getInstance(this).isSafeModeEnabled();
}
}
+18 -144
View File
@@ -89,7 +89,6 @@ import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.L
import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.WARM;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -196,7 +195,6 @@ import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -208,7 +206,6 @@ import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -252,6 +249,8 @@ import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetInflater;
import com.android.launcher3.widget.WidgetInflater.InflationResult;
import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -333,6 +332,7 @@ public class Launcher extends StatefulActivity<LauncherState>
private WidgetManagerHelper mAppWidgetManager;
private LauncherWidgetHolder mAppWidgetHolder;
private WidgetInflater mWidgetInflater;
private final int[] mTmpAddItemCellCoordinates = new int[2];
@@ -517,6 +517,7 @@ public class Launcher extends StatefulActivity<LauncherState>
setupViews();
mAppWidgetManager = new WidgetManagerHelper(this);
mWidgetInflater = new WidgetInflater(this);
mAppWidgetHolder = createAppWidgetHolder();
mAppWidgetHolder.startListening();
mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null));
@@ -2309,157 +2310,30 @@ public class Launcher extends StatefulActivity<LauncherState>
private View inflateAppWidget(LauncherAppWidgetInfo item,
@Nullable LauncherRestoreEventLogger restoreEventLogger) {
if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
item.providerName = QsbContainerView.getSearchComponentName(this);
if (item.providerName == null) {
getModelWriter().deleteItemFromDatabase(item,
"search widget removed because search component cannot be found");
return null;
}
}
final AppWidgetHostView view;
if (mIsSafeModeEnabled) {
view = new PendingAppWidgetHostView(this, item, null, true);
prepareAppWidget(view, item);
return view;
}
TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
try {
final LauncherAppWidgetProviderInfo appWidgetInfo;
String removalReason = "";
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
// If the provider is not ready, bind as a pending widget.
appWidgetInfo = null;
removalReason = "the provider isn't ready.";
} else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
// The widget id is not valid. Try to find the widget based on the provider info.
appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
if (appWidgetInfo == null) {
if (WidgetsModel.GO_DISABLE_WIDGETS) {
removalReason = "widgets are disabled on go device.";
} else {
removalReason =
"WidgetManagerHelper cannot find a provider from provider info.";
}
}
} else {
appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId,
item.getTargetComponent());
if (appWidgetInfo == null) {
if (item.appWidgetId <= CUSTOM_WIDGET_ID) {
removalReason =
"CustomWidgetManager cannot find provider from that widget id.";
} else {
removalReason = "AppWidgetManager cannot find provider for that widget id."
+ " It could be because AppWidgetService is not available, or the"
+ " appWidgetId has not been bound to a the provider yet, or you"
+ " don't have access to that appWidgetId.";
}
InflationResult inflationResult = mWidgetInflater.inflateAppWidget(item);
if (inflationResult.getType() == WidgetInflater.TYPE_DELETE) {
getModelWriter().deleteItemFromDatabase(item, inflationResult.getReason());
if (restoreEventLogger != null) {
restoreEventLogger.logSingleFavoritesItemRestoreFailed(
ITEM_TYPE_APPWIDGET, RESTORE_ERROR_BIND_FAILURE);
}
return null;
}
// If the provider is ready, but the width is not yet restored, try to restore it.
if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
&& (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
if (appWidgetInfo == null) {
getModelWriter().deleteItemFromDatabase(item,
"Removing restored widget: id=" + item.appWidgetId
+ " belongs to component " + item.providerName + " user " + item.user
+ ", as the provider is null and " + removalReason);
if (restoreEventLogger != null) {
restoreEventLogger.logSingleFavoritesItemRestoreFailed(
ITEM_TYPE_APPWIDGET, RESTORE_ERROR_BIND_FAILURE);
}
return null;
}
// If we do not have a valid id, try to bind an id.
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
// Id has not been allocated yet. Allocate a new id.
item.appWidgetId = mAppWidgetHolder.allocateAppWidgetId();
item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED;
// Also try to bind the widget. If the bind fails, the user will be shown
// a click to setup UI, which will ask for the bind permission.
PendingAddWidgetInfo pendingInfo =
new PendingAddWidgetInfo(appWidgetInfo, item.sourceContainer);
pendingInfo.spanX = item.spanX;
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
pendingInfo.minSpanY = item.minSpanY;
Bundle options = pendingInfo.getDefaultSizeOptions(this);
boolean isDirectConfig =
item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
if (isDirectConfig && item.bindOptions != null) {
Bundle newOptions = item.bindOptions.getExtras();
if (options != null) {
newOptions.putAll(options);
}
options = newOptions;
}
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
item.appWidgetId, appWidgetInfo, options);
// We tried to bind once. If we were not able to bind, we would need to
// go through the permission dialog, which means we cannot skip the config
// activity.
item.bindOptions = null;
item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG;
// Bind succeeded
if (success) {
// If the widget has a configure activity, it is still needs to set it
// up, otherwise the widget is ready to go.
item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig
? LauncherAppWidgetInfo.RESTORE_COMPLETED
: LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
}
getModelWriter().updateItemInDatabase(item);
}
} else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
&& (appWidgetInfo.configure == null)) {
// The widget was marked as UI not ready, but there is no configure activity to
// update the UI.
item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
getModelWriter().updateItemInDatabase(item);
}
else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
&& appWidgetInfo.configure != null) {
if (mAppWidgetManager.isAppWidgetRestored(item.appWidgetId)) {
item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
getModelWriter().updateItemInDatabase(item);
}
}
}
if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
// Verify that we own the widget
if (appWidgetInfo == null) {
FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
getModelWriter().deleteWidgetInfo(item, getAppWidgetHolder(), removalReason);
if (restoreEventLogger != null) {
restoreEventLogger.logSingleFavoritesItemRestoreFailed(
ITEM_TYPE_APPWIDGET, RESTORE_ERROR_BIND_FAILURE);
}
return null;
}
item.minSpanX = appWidgetInfo.minSpanX;
item.minSpanY = appWidgetInfo.minSpanY;
view = mAppWidgetHolder.createView(item.appWidgetId, appWidgetInfo);
} else {
view = new PendingAppWidgetHostView(this, item, appWidgetInfo, false);
if (inflationResult.isUpdate()) {
getModelWriter().updateItemInDatabase(item);
}
AppWidgetHostView view = inflationResult.getType() == WidgetInflater.TYPE_PENDING
? new PendingAppWidgetHostView(this, item, inflationResult.getWidgetInfo())
: mAppWidgetHolder.createView(
item.appWidgetId, inflationResult.getWidgetInfo());
prepareAppWidget(view, item);
return view;
} finally {
TraceHelper.INSTANCE.endSection();
}
return view;
}
/**
@@ -56,6 +56,7 @@ import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.widget.custom.CustomWidgetManager;
public class LauncherAppState implements SafeCloseable {
@@ -71,6 +72,8 @@ public class LauncherAppState implements SafeCloseable {
private final LauncherIconProvider mIconProvider;
private final IconCache mIconCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
private boolean mIsSafeModeEnabled;
private final RunnableList mOnTerminateCallback = new RunnableList();
public static LauncherAppState getInstance(final Context context) {
@@ -90,6 +93,8 @@ public class LauncherAppState implements SafeCloseable {
Log.v(Launcher.TAG, "LauncherAppState initiated");
Preconditions.assertUIThread();
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> context.getPackageManager().isSafeMode());
mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
if (modelPropertiesChanged) {
refreshAndReloadLauncher();
@@ -224,6 +229,10 @@ public class LauncherAppState implements SafeCloseable {
return mInvariantDeviceProfile;
}
public boolean isSafeModeEnabled() {
return mIsSafeModeEnabled;
}
/**
* Shorthand for {@link #getInvariantDeviceProfile()}
*/
@@ -88,8 +88,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
private Layout mSetupTextLayout;
public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
@Nullable LauncherAppWidgetProviderInfo appWidget, boolean disabledForSafeMode) {
this(context, info, disabledForSafeMode, appWidget,
@Nullable LauncherAppWidgetProviderInfo appWidget) {
this(context, info, appWidget,
context.getResources().getText(R.string.gadget_complete_setup_text));
super.updateAppWidget(null);
@@ -107,7 +107,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
public PendingAppWidgetHostView(
Context context, int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
this(context, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider), false,
this(context, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider),
appWidget, appWidget.label);
getBackground().mutate().setAlpha(DEFERRED_ALPHA);
@@ -117,14 +117,13 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
}
private PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
boolean disabledForSafeMode, LauncherAppWidgetProviderInfo appwidget,
CharSequence label) {
LauncherAppWidgetProviderInfo appwidget, CharSequence label) {
super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
mAppwidget = appwidget;
mInfo = info;
mStartState = info.restoreStatus;
mDisabledForSafeMode = disabledForSafeMode;
mDisabledForSafeMode = LauncherAppState.getInstance(context).isSafeModeEnabled();
mLabel = label;
mPaint = new TextPaint();
@@ -0,0 +1,196 @@
/*
* Copyright (C) 2024 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 android.content.Context
import com.android.launcher3.Launcher
import com.android.launcher3.LauncherAppState
import com.android.launcher3.logging.FileLog
import com.android.launcher3.model.WidgetsModel
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.qsb.QsbContainerView
/** Utility class for handling widget inflation taking into account all the restore state updates */
class WidgetInflater(private val context: Context) {
private val widgetHelper = WidgetManagerHelper(context)
fun inflateAppWidget(
item: LauncherAppWidgetInfo,
): InflationResult {
if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
item.providerName = QsbContainerView.getSearchComponentName(context)
if (item.providerName == null) {
return InflationResult(
TYPE_DELETE,
reason = "search widget removed because search component cannot be found"
)
}
}
if (LauncherAppState.INSTANCE.get(context).isSafeModeEnabled) {
return InflationResult(TYPE_PENDING)
}
val appWidgetInfo: LauncherAppWidgetProviderInfo?
var removalReason = ""
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
// If the provider is not ready, bind as a pending widget.
appWidgetInfo = null
removalReason = "the provider isn't ready."
} else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
// The widget id is not valid. Try to find the widget based on the provider info.
appWidgetInfo = widgetHelper.findProvider(item.providerName, item.user)
if (appWidgetInfo == null) {
if (WidgetsModel.GO_DISABLE_WIDGETS) {
removalReason = "widgets are disabled on go device."
} else {
removalReason = "WidgetManagerHelper cannot find a provider from provider info."
}
}
} else {
appWidgetInfo =
widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId, item.targetComponent)
if (appWidgetInfo == null) {
if (item.appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
removalReason = "CustomWidgetManager cannot find provider from that widget id."
} else {
removalReason =
("AppWidgetManager cannot find provider for that widget id." +
" It could be because AppWidgetService is not available, or the" +
" appWidgetId has not been bound to a the provider yet, or you" +
" don't have access to that appWidgetId.")
}
}
}
var update = false
// If the provider is ready, but the width is not yet restored, try to restore it.
if (
!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) &&
item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED
) {
if (appWidgetInfo == null) {
return InflationResult(
type = TYPE_DELETE,
reason =
"Removing restored widget: id=${item.appWidgetId} belongs to component" +
" ${item.providerName} user ${item.user}" +
", as the provider is null and $removalReason"
)
}
// If we do not have a valid id, try to bind an id.
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
// Id has not been allocated yet. Allocate a new id.
LauncherWidgetHolder.newInstance(context).let {
item.appWidgetId = it.allocateAppWidgetId()
it.destroy()
}
item.restoreStatus =
item.restoreStatus or LauncherAppWidgetInfo.FLAG_ID_ALLOCATED
// Also try to bind the widget. If the bind fails, the user will be shown
// a click to setup UI, which will ask for the bind permission.
val pendingInfo = PendingAddWidgetInfo(appWidgetInfo, item.sourceContainer)
pendingInfo.spanX = item.spanX
pendingInfo.spanY = item.spanY
pendingInfo.minSpanX = item.minSpanX
pendingInfo.minSpanY = item.minSpanY
var options = pendingInfo.getDefaultSizeOptions(context)
val isDirectConfig =
item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)
if (isDirectConfig && item.bindOptions != null) {
val newOptions = item.bindOptions.extras
if (options != null) {
newOptions!!.putAll(options)
}
options = newOptions
}
val success =
widgetHelper.bindAppWidgetIdIfAllowed(
item.appWidgetId,
appWidgetInfo,
options
)
// We tried to bind once. If we were not able to bind, we would need to
// go through the permission dialog, which means we cannot skip the config
// activity.
item.bindOptions = null
item.restoreStatus =
item.restoreStatus and LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG.inv()
// Bind succeeded
if (success) {
// If the widget has a configure activity, it is still needs to set it
// up, otherwise the widget is ready to go.
item.restoreStatus =
if ((appWidgetInfo.configure == null) || isDirectConfig)
LauncherAppWidgetInfo.RESTORE_COMPLETED
else LauncherAppWidgetInfo.FLAG_UI_NOT_READY
}
update = true
}
} else if (
(item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) &&
(appWidgetInfo.configure == null))
) {
// The widget was marked as UI not ready, but there is no configure activity to
// update the UI.
item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED
update = true
} else if (
(item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) &&
appWidgetInfo.configure != null)
) {
if (widgetHelper.isAppWidgetRestored(item.appWidgetId)) {
item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED
update = true
}
}
}
if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
// Verify that we own the widget
if (appWidgetInfo == null) {
FileLog.e(Launcher.TAG, "Removing invalid widget: id=" + item.appWidgetId)
return InflationResult(TYPE_DELETE, reason = removalReason)
}
item.minSpanX = appWidgetInfo.minSpanX
item.minSpanY = appWidgetInfo.minSpanY
return InflationResult(TYPE_REAL, isUpdate = update, widgetInfo = appWidgetInfo)
} else {
return InflationResult(TYPE_PENDING, isUpdate = update, widgetInfo = appWidgetInfo)
}
}
data class InflationResult(
val type: Int,
val reason: String? = null,
val isUpdate: Boolean = false,
val widgetInfo: LauncherAppWidgetProviderInfo? = null
)
companion object {
const val TYPE_DELETE = 0
const val TYPE_PENDING = 1
const val TYPE_REAL = 2
}
}