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

Commit 380f8fd8 authored by Schneider Victor-tulias's avatar Schneider Victor-tulias
Browse files

Fix ClassCastException in QuickstepModelDelegate

- Added a casting check as an immediate fix to b/173838775
- Added logging to help debug the corrupt state where an item of type other than FolderInfo is used as an other item's container.
- Added LoaderMemoryLogger for adding large logs that are only conditionally printed.

Bug: 173838775
Test: manually checked logs
Change-Id: I9491cb421b9fb807d5fb110b04ad069481de768f
parent 1c487129
Loading
Loading
Loading
Loading
+18 −4
Original line number Diff line number Diff line
@@ -161,8 +161,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
            }
            InstanceId instanceId = new InstanceIdSequence().newInstanceId();
            for (ItemInfo info : itemsIdMap) {
                FolderInfo parent = info.container > 0
                        ? (FolderInfo) itemsIdMap.get(info.container) : null;
                FolderInfo parent = getContainer(info, itemsIdMap);
                StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
            }
            additionalSnapshotEvents(instanceId);
@@ -199,8 +198,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
                        }

                        for (ItemInfo info : itemsIdMap) {
                            FolderInfo parent = info.container > 0
                                    ? (FolderInfo) itemsIdMap.get(info.container) : null;
                            FolderInfo parent = getContainer(info, itemsIdMap);
                            LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
                            Log.d(TAG, itemInfo.toString());
                            StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
@@ -222,6 +220,22 @@ public class QuickstepModelDelegate extends ModelDelegate {
        }
    }

    private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) {
        if (info.container > 0) {
            ItemInfo containerInfo = itemsIdMap.get(info.container);

            if (!(containerInfo instanceof FolderInfo)) {
                Log.e(TAG, String.format(
                        "Item info: %s found with invalid container: %s",
                        info,
                        containerInfo));
            } else {
                return (FolderInfo) containerInfo;
            }
        }
        return null;
    }

    @Override
    public void validateData() {
        super.validateData();
+15 −0
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;

import androidx.annotation.Nullable;

import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Workspace;
@@ -215,6 +217,19 @@ public class BgDataModel {
    }

    public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
        addItem(context, item, newItem, null);
    }

    public synchronized void addItem(
            Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
        if (logger != null) {
            logger.addLog(
                    Log.DEBUG,
                    TAG,
                    String.format("Adding item to ID map: %s", item.toString()),
                    /* stackTrace= */ null);
        }

        itemsIdMap.put(item.id, item);
        switch (item.itemType) {
            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+7 −2
Original line number Diff line number Diff line
@@ -383,18 +383,23 @@ public class LoaderCursor extends CursorWrapper {
        info.cellY = getInt(cellYIndex);
    }

    public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
        checkAndAddItem(info, dataModel, null);
    }

    /**
     * Adds the {@param info} to {@param dataModel} if it does not overlap with any other item,
     * otherwise marks it for deletion.
     */
    public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
    public void checkAndAddItem(
            ItemInfo info, BgDataModel dataModel, LoaderMemoryLogger logger) {
        if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
            // Ensure that it is a valid intent. An exception here will
            // cause the item loading to get skipped
            ShortcutKey.fromItemInfo(info);
        }
        if (checkItemPlacement(info)) {
            dataModel.addItem(mContext, info, false);
            dataModel.addItem(mContext, info, false, logger);
        } else {
            markDeleted("Item position overlap");
        }
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.model;

import android.util.Log;

import androidx.annotation.Nullable;

import java.util.ArrayList;

/**
 * Helper logger that collects logs while {@code LoaderTask#run} executes and prints them all iff
 * an exception is caught in {@code LoaderTask#run}.
 */
public class LoaderMemoryLogger {

    private static final String TAG = "LoaderMemoryLogger";

    private final ArrayList<LogEntry> mLogEntries = new ArrayList<>();

    protected LoaderMemoryLogger() {}

    protected void addLog(int logLevel, String tag, String log) {
        addLog(logLevel, tag, log, null);
    }

    protected void addLog(
            int logLevel, String tag, String log, Exception stackTrace) {
        switch (logLevel) {
            case Log.ASSERT:
            case Log.ERROR:
            case Log.DEBUG:
            case Log.INFO:
            case Log.VERBOSE:
            case Log.WARN:
                mLogEntries.add(new LogEntry(logLevel, tag, log, stackTrace));
                break;
            default:
                throw new IllegalArgumentException("Invalid log level provided: " + logLevel);

        }
    }

    protected void clearLogs() {
        mLogEntries.clear();
    }

    protected void printLogs() {
        for (LogEntry logEntry : mLogEntries) {
            String tag = String.format("%s: %s", TAG, logEntry.mLogTag);
            String logString = logEntry.mStackStrace == null
                    ? logEntry.mLogString
                    : String.format(
                            "%s\n%s",
                            logEntry.mLogString,
                            Log.getStackTraceString(logEntry.mStackStrace));

            Log.println(logEntry.mLogLevel, tag, logString);
        }
        clearLogs();
    }

    private static class LogEntry {

        protected final int mLogLevel;
        protected final String mLogTag;
        protected final String mLogString;
        @Nullable protected final Exception mStackStrace;

        protected LogEntry(
                int logLevel, String logTag, String logString, @Nullable Exception stackStrace) {
            mLogLevel = logLevel;
            mLogTag = logTag;
            mLogString = logString;
            mStackStrace = stackStrace;
        }
    }
}
+22 −7
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ import android.util.Log;
import android.util.LongSparseArray;
import android.util.TimingLogger;

import androidx.annotation.Nullable;

import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
@@ -197,11 +199,12 @@ public class LoaderTask implements Runnable {

        Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
        TimingLogger logger = new TimingLogger(TAG, "run");
        LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
        try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
            List<ShortcutInfo> allShortcuts = new ArrayList<>();
            Trace.beginSection("LoadWorkspace");
            try {
                loadWorkspace(allShortcuts);
                loadWorkspace(allShortcuts, memoryLogger);
            } finally {
                Trace.endSection();
            }
@@ -311,9 +314,13 @@ public class LoaderTask implements Runnable {

            mModelDelegate.modelLoadComplete();
            transaction.commit();
            memoryLogger.clearLogs();
        } catch (CancellationException e) {
            // Loader stopped, ignore
            logASplit(logger, "Cancelled");
        } catch (Exception e) {
            memoryLogger.printLogs();
            throw e;
        } finally {
            logger.dumpToLog();
        }
@@ -325,13 +332,21 @@ public class LoaderTask implements Runnable {
        this.notify();
    }

    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger logger) {
        loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI,
                null /* selection */);
                null /* selection */, logger);
    }

    protected void loadWorkspace(
            List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
        loadWorkspace(allDeepShortcuts, contentUri, selection, null);
    }

    protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri,
            String selection) {
    protected void loadWorkspace(
            List<ShortcutInfo> allDeepShortcuts,
            Uri contentUri,
            String selection,
            @Nullable LoaderMemoryLogger logger) {
        final Context context = mApp.getContext();
        final ContentResolver contentResolver = context.getContentResolver();
        final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -635,7 +650,7 @@ public class LoaderTask implements Runnable {
                                        }
                                }

                                c.checkAndAddItem(info, mBgDataModel);
                                c.checkAndAddItem(info, mBgDataModel, logger);
                            } else {
                                throw new RuntimeException("Unexpected null WorkspaceItemInfo");
                            }
@@ -654,7 +669,7 @@ public class LoaderTask implements Runnable {
                            // no special handling required for restored folders
                            c.markRestored();

                            c.checkAndAddItem(folderInfo, mBgDataModel);
                            c.checkAndAddItem(folderInfo, mBgDataModel, logger);
                            break;

                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: