Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 31e27ed9 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Moving widget inflation logic to a separate class

This allows us to reuse this logic at other places

Bug: 318539160
Test: atest TaplBinderTests
Flag: None
Change-Id: I2e19aad207efb3a99bb3b70ba71c076699dba2bc
parent 40e7937a
Loading
Loading
Loading
Loading
+1 −6
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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,64 +2310,11 @@ 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.";
                    }
                }
            }

            // 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);
            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);
@@ -2374,92 +2322,18 @@ public class Launcher extends StatefulActivity<LauncherState>
                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;
            if (inflationResult.isUpdate()) {
                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);
            }
            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;
    }

    /**
+9 −0
Original line number Diff line number Diff line
@@ -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()}
     */
+5 −6
Original line number Diff line number Diff line
@@ -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();
+196 −0
Original line number Diff line number Diff line
/*
 * 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
    }
}