diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index f8ed4df453..b6f66152dc 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -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(); } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 5c49b89b00..152ad5f318 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -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 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 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 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; } /** diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 5ae2d71a39..d4bcd24019 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -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()} */ diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 6ad913e695..25979c268d 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -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(); diff --git a/src/com/android/launcher3/widget/WidgetInflater.kt b/src/com/android/launcher3/widget/WidgetInflater.kt new file mode 100644 index 0000000000..00707f5969 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetInflater.kt @@ -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 + } +}