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

Commit 50308f37 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fixing Launcher preview leaking surface and memory" into sc-dev

parents 07414d5c df4241ca
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -81,6 +81,8 @@ public class LauncherAppState {

    public LauncherAppState(Context context) {
        this(context, LauncherFiles.APP_ICONS_DB);
        Log.v(Launcher.TAG, "LauncherAppState initiated");
        Preconditions.assertUIThread();

        mInvariantDeviceProfile.addOnChangeListener(idp -> refreshAndReloadLauncher());

@@ -132,8 +134,6 @@ public class LauncherAppState {
    }

    public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
        Log.v(Launcher.TAG, "LauncherAppState initiated");
        Preconditions.assertUIThread();
        mContext = context;

        mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
@@ -142,6 +142,7 @@ public class LauncherAppState {
                iconCacheFileName, mIconProvider);
        mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
        mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
        mOnTerminateCallback.add(mIconCache::close);
    }

    private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
+82 −2
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import static com.android.launcher3.Utilities.getPrefs;
import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS;
import static com.android.launcher3.util.Themes.isThemedIconEnabled;

import android.annotation.TargetApi;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.pm.PackageManager;
@@ -12,14 +13,24 @@ import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Xml;

import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.GridOption;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Executors;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -65,6 +76,11 @@ public class GridCustomizationsProvider extends ContentProvider {
    private static final String ICON_THEMED = "/icon_themed";
    private static final String BOOLEAN_VALUE = "boolean_value";

    private static final String KEY_SURFACE_PACKAGE = "surface_package";
    private static final String KEY_CALLBACK = "callback";

    private final ArrayMap<IBinder, PreviewLifecycleObserver> mActivePreviews = new ArrayMap<>();

    @Override
    public boolean onCreate() {
        return true;
@@ -177,10 +193,74 @@ public class GridCustomizationsProvider extends ContentProvider {
            return null;
        }

        if (!METHOD_GET_PREVIEW.equals(method)) {
        if (!Utilities.ATLEAST_R || !METHOD_GET_PREVIEW.equals(method)) {
            return null;
        }
        return getPreview(extras);
    }

    @TargetApi(Build.VERSION_CODES.R)
    private synchronized Bundle getPreview(Bundle request) {
        PreviewLifecycleObserver observer = null;
        try {
            PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(getContext(), request);

            // Destroy previous
            destroyObserver(mActivePreviews.get(renderer.getHostToken()));

            observer = new PreviewLifecycleObserver(renderer);
            mActivePreviews.put(renderer.getHostToken(), observer);

            renderer.loadAsync();
            renderer.getHostToken().linkToDeath(observer, 0);

            Bundle result = new Bundle();
            result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage());

            Messenger messenger = new Messenger(new Handler(Looper.getMainLooper(), observer));
            Message msg = Message.obtain();
            msg.replyTo = messenger;
            result.putParcelable(KEY_CALLBACK, msg);
            return result;
        } catch (Exception e) {
            Log.e(TAG, "Unable to generate preview", e);
            if (observer != null) {
                destroyObserver(observer);
            }
            return null;
        }
    }

        return new PreviewSurfaceRenderer(getContext(), extras).render();
    private synchronized void destroyObserver(PreviewLifecycleObserver observer) {
        if (observer == null || observer.destroyed) {
            return;
        }
        observer.destroyed = true;
        Executors.MAIN_EXECUTOR.execute(observer.renderer::destroy);
        PreviewLifecycleObserver cached = mActivePreviews.get(observer.renderer.getHostToken());
        if (cached == observer) {
            mActivePreviews.remove(observer.renderer.getHostToken());
        }
    }

    private class PreviewLifecycleObserver implements Handler.Callback, DeathRecipient {

        public final PreviewSurfaceRenderer renderer;
        public boolean destroyed = false;

        PreviewLifecycleObserver(PreviewSurfaceRenderer renderer) {
            this.renderer = renderer;
        }

        @Override
        public boolean handleMessage(Message message) {
            destroyObserver(this);
            return true;
        }

        @Override
        public void binderDied() {
            destroyObserver(this);
        }
    }
}
+28 −172
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;

import android.annotation.TargetApi;
import android.app.Fragment;
@@ -32,7 +31,6 @@ import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
@@ -43,7 +41,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
@@ -57,8 +54,6 @@ import com.android.launcher3.Hotseat;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceLayoutManager;
@@ -67,13 +62,8 @@ import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDelegate;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.FolderInfo;
@@ -100,13 +90,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -120,8 +104,6 @@ import java.util.concurrent.TimeoutException;
public class LauncherPreviewRenderer extends ContextWrapper
        implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {

    private static final String TAG = "LauncherPreviewRenderer";

    /**
     * Context used just for preview. It also provides a few objects (e.g. UserCache) just for
     * preview purposes.
@@ -138,9 +120,15 @@ public class LauncherPreviewRenderer extends ContextWrapper
        private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
                new ConcurrentLinkedQueue<>();

        private boolean mDestroyed = false;

        public PreviewContext(Context base, InvariantDeviceProfile idp) {
            super(base);
            mIdp = idp;
            mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp);
            mObjectMap.put(LauncherAppState.INSTANCE,
                    new LauncherAppState(this, null /* iconCacheFileName */));

        }

        @Override
@@ -149,11 +137,9 @@ public class LauncherPreviewRenderer extends ContextWrapper
        }

        public void onDestroy() {
            CustomWidgetManager customWidgetManager = (CustomWidgetManager) mObjectMap.get(
                    CustomWidgetManager.INSTANCE);
            if (customWidgetManager != null) {
                customWidgetManager.onDestroy();
            }
            CustomWidgetManager.INSTANCE.get(this).onDestroy();
            LauncherAppState.INSTANCE.get(this).onTerminate();
            mDestroyed = true;
        }

        /**
@@ -162,17 +148,12 @@ public class LauncherPreviewRenderer extends ContextWrapper
         */
        public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
                MainThreadInitializedObject.ObjectProvider<T> provider) {
            if (FeatureFlags.IS_STUDIO_BUILD && mDestroyed) {
                throw new RuntimeException("Context already destroyed");
            }
            if (!mAllowedObjects.contains(mainThreadInitializedObject)) {
                throw new IllegalStateException("Leaking unknown objects");
            }
            if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
                throw new IllegalStateException(
                        "Should not use MainThreadInitializedObject to initialize this with "
                                + "PreviewContext");
            }
            if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) {
                return (T) mIdp;
            }
            if (mObjectMap.containsKey(mainThreadInitializedObject)) {
                return (T) mObjectMap.get(mainThreadInitializedObject);
            }
@@ -210,7 +191,6 @@ public class LauncherPreviewRenderer extends ContextWrapper
    private final Context mContext;
    private final InvariantDeviceProfile mIdp;
    private final DeviceProfile mDp;
    private final boolean mMigrated;
    private final Rect mInsets;
    private final WorkspaceItemInfo mWorkspaceItemInfo;
    private final LayoutInflater mHomeElementInflater;
@@ -218,13 +198,12 @@ public class LauncherPreviewRenderer extends ContextWrapper
    private final Hotseat mHotseat;
    private final CellLayout mWorkspace;

    public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) {
    public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
        super(context);
        mUiHandler = new Handler(Looper.getMainLooper());
        mContext = context;
        mIdp = idp;
        mDp = idp.getDeviceProfile(context).copy(context);
        mMigrated = migrated;

        // TODO: get correct insets once display cutout API is available.
        mInsets = new Rect();
@@ -265,8 +244,9 @@ public class LauncherPreviewRenderer extends ContextWrapper
    }

    /** Populate preview and render it. */
    public View getRenderedView() {
        populate();
    public View getRenderedView(BgDataModel dataModel,
            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
        populate(dataModel, widgetProviderInfoMap);
        return mRootView;
    }

@@ -392,38 +372,17 @@ public class LauncherPreviewRenderer extends ContextWrapper
        }
    }

    private void populate() {
        WorkspaceFetcher fetcher;
        PreviewContext previewContext = null;
        if (mMigrated) {
            previewContext = new PreviewContext(mContext, mIdp);
            LauncherAppState appForPreview = new LauncherAppState(
                    previewContext, null /* iconCacheFileName */);
            fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
            MODEL_EXECUTOR.execute(fetcher);
        } else {
            fetcher = new WorkspaceItemsInfoFetcher();
            LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
                    (LauncherModel.ModelUpdateTask) fetcher);
        }
        WorkspaceResult workspaceResult = fetcher.get();
        if (previewContext != null) {
            previewContext.onDestroy();
        }

        if (workspaceResult == null) {
            return;
        }

    private void populate(BgDataModel dataModel,
            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
        // Separate the items that are on the current screen, and the other remaining items.
        ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
        ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
        ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
        ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
        filterCurrentWorkspaceItems(0 /* currentScreenId */,
                workspaceResult.mWorkspaceItems, currentWorkspaceItems,
                dataModel.workspaceItems, currentWorkspaceItems,
                otherWorkspaceItems);
        filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
        filterCurrentWorkspaceItems(0 /* currentScreenId */, dataModel.appWidgets,
                currentAppWidgets, otherAppWidgets);
        sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
        for (ItemInfo itemInfo : currentWorkspaceItems) {
@@ -444,12 +403,12 @@ public class LauncherPreviewRenderer extends ContextWrapper
            switch (itemInfo.itemType) {
                case Favorites.ITEM_TYPE_APPWIDGET:
                case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
                    if (mMigrated) {
                        inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
                                workspaceResult.mWidgetProvidersMap);
                    if (widgetProviderInfoMap != null) {
                        inflateAndAddWidgets(
                                (LauncherAppWidgetInfo) itemInfo, widgetProviderInfoMap);
                    } else {
                        inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
                                workspaceResult.mWidgetsModel);
                                dataModel.widgetsModel);
                    }
                    break;
                default:
@@ -458,8 +417,10 @@ public class LauncherPreviewRenderer extends ContextWrapper
        }
        IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
                mDp.numShownHotseatIcons);
        List<ItemInfo> predictions = workspaceResult.mHotseatPredictions == null
                ? Collections.emptyList() : workspaceResult.mHotseatPredictions.items;
        FixedContainerItems hotseatpredictions =
                dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
        List<ItemInfo> predictions = hotseatpredictions == null
                ? Collections.emptyList() : hotseatpredictions.items;
        int count = Math.min(ranks.size(), predictions.size());
        for (int i = 0; i < count; i++) {
            int rank = ranks.get(i);
@@ -494,109 +455,4 @@ public class LauncherPreviewRenderer extends ContextWrapper
        view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
        view.layout(0, 0, width, height);
    }

    private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask,
            WorkspaceFetcher {

        private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);

        private LauncherAppState mApp;
        private LauncherModel mModel;
        private BgDataModel mBgDataModel;
        private AllAppsList mAllAppsList;

        @Override
        public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
                AllAppsList allAppsList, Executor uiExecutor) {
            mApp = app;
            mModel = model;
            mBgDataModel = dataModel;
            mAllAppsList = allAppsList;
        }

        @Override
        public FutureTask<WorkspaceResult> getTask() {
            return mTask;
        }

        @Override
        public void run() {
            mTask.run();
        }

        @Override
        public WorkspaceResult call() throws Exception {
            if (!mModel.isModelLoaded()) {
                Log.d(TAG, "Workspace not loaded, loading now");
                mModel.startLoaderForResults(
                        new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
                return null;
            }

            return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null);
        }
    }

    private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements
            WorkspaceFetcher {

        private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);

        WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
            super(app, null, new BgDataModel(), new ModelDelegate(), null);
        }

        @Override
        public FutureTask<WorkspaceResult> getTask() {
            return mTask;
        }

        @Override
        public void run() {
            mTask.run();
        }

        @Override
        public WorkspaceResult call() {
            List<ShortcutInfo> allShortcuts = new ArrayList<>();
            loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
                    LauncherSettings.Favorites.SCREEN + " = 0 or "
                            + LauncherSettings.Favorites.CONTAINER + " = "
                            + LauncherSettings.Favorites.CONTAINER_HOTSEAT);
            return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap);
        }
    }

    private interface WorkspaceFetcher extends Runnable, Callable<WorkspaceResult> {
        FutureTask<WorkspaceResult> getTask();

        default WorkspaceResult get() {
            try {
                return getTask().get(5, TimeUnit.SECONDS);
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                Log.d(TAG, "Error fetching workspace items info", e);
                return null;
            }
        }
    }

    private static class WorkspaceResult {
        private final ArrayList<ItemInfo> mWorkspaceItems;
        private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
        private final FixedContainerItems mHotseatPredictions;
        private final WidgetsModel mWidgetsModel;
        private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;

        private WorkspaceResult(BgDataModel dataModel,
                WidgetsModel widgetsModel,
                Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
            synchronized (dataModel) {
                mWorkspaceItems = dataModel.workspaceItems;
                mAppWidgets = dataModel.appWidgets;
                mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
                mWidgetsModel = widgetsModel;
                mWidgetProvidersMap = widgetProviderInfoMap;
            }
        }
    }
}
+125 −93

File changed.

Preview size limit exceeded, changes collapsed.

+7 −0
Original line number Diff line number Diff line
@@ -130,6 +130,13 @@ public class IconCache extends BaseIconCache {
        }
    }

    /**
     * Closes the cache DB. This will clear any in-memory cache.
     */
    public void close() {
        mIconDb.close();
    }

    /**
     * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
     *
Loading