Caching last predictions and loading it with model
Adding support for persisting itemInfos on disk. This uses a separate xml file. Unlike prefs, it does not keep the items in memory and is just a wraper over reading/writing a file. Bug: 160748731 Change-Id: Iaccab9928ab8f30127fb3c2d630ca8ca83f0bd05
This commit is contained in:
+21
-19
@@ -21,6 +21,7 @@ import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKE
|
||||
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
@@ -29,6 +30,7 @@ import android.os.UserHandle;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
|
||||
@@ -43,27 +45,22 @@ import java.util.stream.Collectors;
|
||||
public class PredictionUpdateTask extends BaseModelUpdateTask {
|
||||
|
||||
private final List<AppTarget> mTargets;
|
||||
private final int mContainerId;
|
||||
private final PredictorState mPredictorState;
|
||||
|
||||
PredictionUpdateTask(int containerId, List<AppTarget> targets) {
|
||||
mContainerId = containerId;
|
||||
PredictionUpdateTask(PredictorState predictorState, List<AppTarget> targets) {
|
||||
mPredictorState = predictorState;
|
||||
mTargets = targets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
||||
// TODO: persist the whole list
|
||||
Utilities.getDevicePrefs(app.getContext()).edit()
|
||||
Context context = app.getContext();
|
||||
|
||||
// TODO: remove this
|
||||
Utilities.getDevicePrefs(context).edit()
|
||||
.putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
|
||||
|
||||
FixedContainerItems fci;
|
||||
synchronized (dataModel) {
|
||||
fci = dataModel.extraItems.get(mContainerId);
|
||||
if (fci == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FixedContainerItems fci = mPredictorState.items;
|
||||
Set<UserHandle> usersForChangedShortcuts = new HashSet<>(fci.items.stream()
|
||||
.filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT)
|
||||
.map(info -> info.user)
|
||||
@@ -75,7 +72,7 @@ public class PredictionUpdateTask extends BaseModelUpdateTask {
|
||||
ShortcutInfo si = target.getShortcutInfo();
|
||||
if (si != null) {
|
||||
usersForChangedShortcuts.add(si.getUserHandle());
|
||||
itemInfo = new WorkspaceItemInfo(si, app.getContext());
|
||||
itemInfo = new WorkspaceItemInfo(si, context);
|
||||
app.getIconCache().getShortcutIcon(itemInfo, si);
|
||||
} else {
|
||||
String className = target.getClassName();
|
||||
@@ -87,16 +84,18 @@ public class PredictionUpdateTask extends BaseModelUpdateTask {
|
||||
UserHandle user = target.getUser();
|
||||
itemInfo = apps.data.stream()
|
||||
.filter(info -> user.equals(info.user) && cn.equals(info.componentName))
|
||||
.map(AppInfo::makeWorkspaceItem)
|
||||
.map(ai -> {
|
||||
app.getIconCache().getTitleAndIcon(ai, false);
|
||||
return ai.makeWorkspaceItem();
|
||||
})
|
||||
.findAny()
|
||||
.orElseGet(() -> {
|
||||
LauncherActivityInfo lai = app.getContext()
|
||||
.getSystemService(LauncherApps.class)
|
||||
LauncherActivityInfo lai = context.getSystemService(LauncherApps.class)
|
||||
.resolveActivity(AppInfo.makeLaunchIntent(cn), user);
|
||||
if (lai == null) {
|
||||
return null;
|
||||
}
|
||||
AppInfo ai = new AppInfo(app.getContext(), lai, user);
|
||||
AppInfo ai = new AppInfo(context, lai, user);
|
||||
app.getIconCache().getTitleAndIcon(ai, lai, false);
|
||||
return ai.makeWorkspaceItem();
|
||||
});
|
||||
@@ -106,12 +105,15 @@ public class PredictionUpdateTask extends BaseModelUpdateTask {
|
||||
}
|
||||
}
|
||||
|
||||
itemInfo.container = mContainerId;
|
||||
itemInfo.container = fci.containerId;
|
||||
fci.items.add(itemInfo);
|
||||
}
|
||||
|
||||
bindExtraContainerItems(fci);
|
||||
usersForChangedShortcuts.forEach(
|
||||
u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
|
||||
|
||||
// Save to disk
|
||||
mPredictorState.storage.write(context, fci.items);
|
||||
}
|
||||
}
|
||||
|
||||
+156
-21
@@ -17,6 +17,8 @@ package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
|
||||
import android.app.prediction.AppPredictionContext;
|
||||
import android.app.prediction.AppPredictionManager;
|
||||
@@ -24,16 +26,32 @@ import android.app.prediction.AppPredictor;
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.AppTargetEvent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.PersistedItemArray;
|
||||
import com.android.quickstep.logging.StatsLogCompatManager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Model delegate which loads prediction items
|
||||
@@ -42,10 +60,12 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
|
||||
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
|
||||
|
||||
private final PredictorState mAllAppsState =
|
||||
new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
|
||||
|
||||
private final InvariantDeviceProfile mIDP;
|
||||
private final AppEventProducer mAppEventProducer;
|
||||
|
||||
private AppPredictor mAllAppsPredictor;
|
||||
private boolean mActive = false;
|
||||
|
||||
public QuickstepModelDelegate(Context context) {
|
||||
@@ -57,11 +77,15 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadItems() {
|
||||
public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
|
||||
// TODO: Implement caching and preloading
|
||||
super.loadItems();
|
||||
mDataModel.extraItems.put(
|
||||
CONTAINER_PREDICTION, new FixedContainerItems(CONTAINER_PREDICTION));
|
||||
super.loadItems(ums, pinnedShortcuts);
|
||||
|
||||
WorkspaceItemFactory factory =
|
||||
new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numAllAppsColumns);
|
||||
mAllAppsState.items.setItems(
|
||||
mAllAppsState.storage.read(mApp.getContext(), factory, ums.allUsers::get));
|
||||
mDataModel.extraItems.put(CONTAINER_PREDICTION, mAllAppsState.items);
|
||||
|
||||
mActive = true;
|
||||
recreatePredictors();
|
||||
@@ -70,8 +94,8 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
@Override
|
||||
public void validateData() {
|
||||
super.validateData();
|
||||
if (mAllAppsPredictor != null) {
|
||||
mAllAppsPredictor.requestPredictionUpdate();
|
||||
if (mAllAppsState.predictor != null) {
|
||||
mAllAppsState.predictor.requestPredictionUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,10 +110,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
}
|
||||
|
||||
private void destroyPredictors() {
|
||||
if (mAllAppsPredictor != null) {
|
||||
mAllAppsPredictor.destroy();
|
||||
mAllAppsPredictor = null;
|
||||
}
|
||||
mAllAppsState.destroyPredictor();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -98,7 +119,6 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
if (!mActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = mApp.getContext();
|
||||
AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
|
||||
if (apm == null) {
|
||||
@@ -107,19 +127,23 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
|
||||
int count = mIDP.numAllAppsColumns;
|
||||
|
||||
mAllAppsPredictor = apm.createAppPredictionSession(
|
||||
mAllAppsState.predictor = apm.createAppPredictionSession(
|
||||
new AppPredictionContext.Builder(context)
|
||||
.setUiSurface("home")
|
||||
.setPredictedTargetCount(count)
|
||||
.build());
|
||||
mAllAppsPredictor.registerPredictionUpdates(
|
||||
Executors.MODEL_EXECUTOR, this::onAllAppsPredictionChanged);
|
||||
mAllAppsPredictor.requestPredictionUpdate();
|
||||
mAllAppsState.predictor.registerPredictionUpdates(
|
||||
Executors.MODEL_EXECUTOR, t -> handleUpdate(mAllAppsState, t));
|
||||
mAllAppsState.predictor.requestPredictionUpdate();
|
||||
}
|
||||
|
||||
private void onAllAppsPredictionChanged(List<AppTarget> targets) {
|
||||
mApp.getModel().enqueueModelUpdateTask(
|
||||
new PredictionUpdateTask(CONTAINER_PREDICTION, targets));
|
||||
|
||||
private void handleUpdate(PredictorState state, List<AppTarget> targets) {
|
||||
if (state.setTargets(targets)) {
|
||||
// No diff, skip
|
||||
return;
|
||||
}
|
||||
mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,8 +155,119 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
}
|
||||
|
||||
private void onAppTargetEvent(AppTargetEvent event) {
|
||||
if (mAllAppsPredictor != null) {
|
||||
mAllAppsPredictor.notifyAppTargetEvent(event);
|
||||
if (mAllAppsState.predictor != null) {
|
||||
mAllAppsState.predictor.notifyAppTargetEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
static class PredictorState {
|
||||
|
||||
public final FixedContainerItems items;
|
||||
public final PersistedItemArray storage;
|
||||
public AppPredictor predictor;
|
||||
|
||||
private List<AppTarget> mLastTargets;
|
||||
|
||||
PredictorState(int container, String storageName) {
|
||||
items = new FixedContainerItems(container);
|
||||
storage = new PersistedItemArray(storageName);
|
||||
mLastTargets = Collections.emptyList();
|
||||
}
|
||||
|
||||
public void destroyPredictor() {
|
||||
if (predictor != null) {
|
||||
predictor.destroy();
|
||||
predictor = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new targets and returns true if it was different than before.
|
||||
*/
|
||||
boolean setTargets(List<AppTarget> newTargets) {
|
||||
List<AppTarget> oldTargets = mLastTargets;
|
||||
mLastTargets = newTargets;
|
||||
|
||||
int size = oldTargets.size();
|
||||
return size == newTargets.size() && IntStream.range(0, size)
|
||||
.allMatch(i -> areAppTargetsSame(oldTargets.get(i), newTargets.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two targets for the properties which we care about
|
||||
*/
|
||||
private static boolean areAppTargetsSame(AppTarget t1, AppTarget t2) {
|
||||
if (!Objects.equals(t1.getPackageName(), t2.getPackageName())
|
||||
|| !Objects.equals(t1.getUser(), t2.getUser())
|
||||
|| !Objects.equals(t1.getClassName(), t2.getClassName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ShortcutInfo s1 = t1.getShortcutInfo();
|
||||
ShortcutInfo s2 = t2.getShortcutInfo();
|
||||
if (s1 != null) {
|
||||
if (s2 == null || !Objects.equals(s1.getId(), s2.getId())) {
|
||||
return false;
|
||||
}
|
||||
} else if (s2 != null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory {
|
||||
|
||||
private final LauncherAppState mAppState;
|
||||
private final UserManagerState mUMS;
|
||||
private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
|
||||
private final int mMaxCount;
|
||||
|
||||
private int mReadCount = 0;
|
||||
|
||||
protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
|
||||
Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount) {
|
||||
mAppState = appState;
|
||||
mUMS = ums;
|
||||
mPinnedShortcuts = pinnedShortcuts;
|
||||
mMaxCount = maxCount;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ItemInfo createInfo(int itemType, UserHandle user, Intent intent) {
|
||||
if (mReadCount >= mMaxCount) {
|
||||
return null;
|
||||
}
|
||||
switch (itemType) {
|
||||
case ITEM_TYPE_APPLICATION: {
|
||||
LauncherActivityInfo lai = mAppState.getContext()
|
||||
.getSystemService(LauncherApps.class)
|
||||
.resolveActivity(intent, user);
|
||||
if (lai == null) {
|
||||
return null;
|
||||
}
|
||||
AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user));
|
||||
mAppState.getIconCache().getTitleAndIcon(info, lai, false);
|
||||
mReadCount++;
|
||||
return info.makeWorkspaceItem();
|
||||
}
|
||||
case ITEM_TYPE_DEEP_SHORTCUT: {
|
||||
ShortcutKey key = ShortcutKey.fromIntent(intent, user);
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
ShortcutInfo si = mPinnedShortcuts.get(key);
|
||||
if (si == null) {
|
||||
return null;
|
||||
}
|
||||
WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext());
|
||||
mAppState.getIconCache().getShortcutIcon(wii, si);
|
||||
mReadCount++;
|
||||
return wii;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ import android.os.Process;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.util.Executors;
|
||||
@@ -92,9 +92,9 @@ public class LoaderCursorTest {
|
||||
SCREEN, CELLX, CELLY, RESTORED, INTENT
|
||||
});
|
||||
|
||||
mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp,
|
||||
new UserManagerState());
|
||||
mLoaderCursor.allUsers.put(0, Process.myUserHandle());
|
||||
UserManagerState ums = new UserManagerState();
|
||||
mLoaderCursor = new LoaderCursor(mCursor, Favorites.CONTENT_URI, mApp, ums);
|
||||
ums.allUsers.put(0, Process.myUserHandle());
|
||||
}
|
||||
|
||||
private void initCursor(int itemType, String title) {
|
||||
|
||||
@@ -658,7 +658,7 @@ public class AutoInstallsLayout {
|
||||
}
|
||||
}
|
||||
|
||||
protected static void beginDocument(XmlPullParser parser, String firstElementName)
|
||||
public static void beginDocument(XmlPullParser parser, String firstElementName)
|
||||
throws XmlPullParserException, IOException {
|
||||
int type;
|
||||
while ((type = parser.next()) != XmlPullParser.START_TAG
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel.CallbackTask;
|
||||
import com.android.launcher3.PagedView;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
@@ -76,18 +77,20 @@ public abstract class BaseLoaderResults {
|
||||
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
|
||||
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
|
||||
final IntArray orderedScreenIds = new IntArray();
|
||||
ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
|
||||
|
||||
synchronized (mBgDataModel) {
|
||||
workspaceItems.addAll(mBgDataModel.workspaceItems);
|
||||
appWidgets.addAll(mBgDataModel.appWidgets);
|
||||
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
|
||||
mBgDataModel.extraItems.forEach(extraItems::add);
|
||||
mBgDataModel.lastBindId++;
|
||||
mMyBindingId = mBgDataModel.lastBindId;
|
||||
}
|
||||
|
||||
for (Callbacks cb : mCallbacksList) {
|
||||
new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
|
||||
workspaceItems, appWidgets, orderedScreenIds).bind();
|
||||
workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +138,7 @@ public abstract class BaseLoaderResults {
|
||||
private final ArrayList<ItemInfo> mWorkspaceItems;
|
||||
private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
|
||||
private final IntArray mOrderedScreenIds;
|
||||
|
||||
private final ArrayList<FixedContainerItems> mExtraItems;
|
||||
|
||||
WorkspaceBinder(Callbacks callbacks,
|
||||
Executor uiExecutor,
|
||||
@@ -144,6 +147,7 @@ public abstract class BaseLoaderResults {
|
||||
int myBindingId,
|
||||
ArrayList<ItemInfo> workspaceItems,
|
||||
ArrayList<LauncherAppWidgetInfo> appWidgets,
|
||||
ArrayList<FixedContainerItems> extraItems,
|
||||
IntArray orderedScreenIds) {
|
||||
mCallbacks = callbacks;
|
||||
mUiExecutor = uiExecutor;
|
||||
@@ -152,6 +156,7 @@ public abstract class BaseLoaderResults {
|
||||
mMyBindingId = myBindingId;
|
||||
mWorkspaceItems = workspaceItems;
|
||||
mAppWidgets = appWidgets;
|
||||
mExtraItems = extraItems;
|
||||
mOrderedScreenIds = orderedScreenIds;
|
||||
}
|
||||
|
||||
@@ -198,6 +203,8 @@ public abstract class BaseLoaderResults {
|
||||
// Load items on the current page.
|
||||
bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
|
||||
bindAppWidgets(currentAppWidgets, mainExecutor);
|
||||
mExtraItems.forEach(item ->
|
||||
executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
|
||||
|
||||
// Locate available spots for prediction using currentWorkspaceItems
|
||||
IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons);
|
||||
@@ -207,6 +214,7 @@ public abstract class BaseLoaderResults {
|
||||
// happens later).
|
||||
// This ensures that the first screen is immediately visible (eg. during rotation)
|
||||
// In case of !validFirstPage, bind all pages one after other.
|
||||
|
||||
final Executor deferredExecutor =
|
||||
validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
|
||||
|
||||
|
||||
@@ -424,6 +424,14 @@ public class BgDataModel {
|
||||
public FixedContainerItems clone() {
|
||||
return new FixedContainerItems(containerId, new ArrayList<>(items));
|
||||
}
|
||||
|
||||
public void setItems(List<ItemInfo> newItems) {
|
||||
items.clear();
|
||||
newItems.forEach(item -> {
|
||||
item.container = containerId;
|
||||
items.add(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public class LoaderCursor extends CursorWrapper {
|
||||
|
||||
private static final String TAG = "LoaderCursor";
|
||||
|
||||
public final LongSparseArray<UserHandle> allUsers;
|
||||
private final LongSparseArray<UserHandle> allUsers;
|
||||
|
||||
private final Uri mContentUri;
|
||||
private final Context mContext;
|
||||
|
||||
@@ -360,15 +360,12 @@ public class LoaderTask implements Runnable {
|
||||
final int optionsIndex = c.getColumnIndexOrThrow(
|
||||
LauncherSettings.Favorites.OPTIONS);
|
||||
|
||||
final LongSparseArray<UserHandle> allUsers = c.allUsers;
|
||||
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
|
||||
|
||||
mUserManagerState.init(mUserCache, mUserManager);
|
||||
|
||||
for (UserHandle user : mUserCache.getUserProfiles()) {
|
||||
long serialNo = mUserCache.getSerialNumberForUser(user);
|
||||
allUsers.put(serialNo, user);
|
||||
|
||||
boolean userUnlocked = mUserManager.isUserUnlocked(user);
|
||||
|
||||
// We can only query for shortcuts when the user is unlocked.
|
||||
@@ -419,16 +416,6 @@ public class LoaderTask implements Runnable {
|
||||
ComponentName cn = intent.getComponent();
|
||||
targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
|
||||
|
||||
if (allUsers.indexOfValue(c.user) < 0) {
|
||||
if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
|
||||
c.markDeleted("Legacy shortcuts are only allowed for current users");
|
||||
continue;
|
||||
} else if (c.restoreFlag != 0) {
|
||||
// Don't restore items for other profiles.
|
||||
c.markDeleted("Restore from other profiles not supported");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (TextUtils.isEmpty(targetPkg) &&
|
||||
c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
|
||||
c.markDeleted("Only legacy shortcuts can have null package");
|
||||
@@ -773,7 +760,7 @@ public class LoaderTask implements Runnable {
|
||||
}
|
||||
|
||||
// Load delegate items
|
||||
mModelDelegate.loadItems();
|
||||
mModelDelegate.loadItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
|
||||
|
||||
// Break early if we've stopped loading
|
||||
if (mStopped) {
|
||||
|
||||
@@ -18,13 +18,17 @@ package com.android.launcher3.model;
|
||||
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.ResourceBasedOverride;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class to extend LauncherModel functionality to provide extra data
|
||||
*/
|
||||
@@ -65,11 +69,12 @@ public class ModelDelegate implements ResourceBasedOverride {
|
||||
* Load delegate items if any in the data model
|
||||
*/
|
||||
@WorkerThread
|
||||
public void loadItems() { }
|
||||
public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
|
||||
|
||||
/**
|
||||
* Called when the delegate is no loner needed
|
||||
*/
|
||||
@WorkerThread
|
||||
public void destroy() { }
|
||||
|
||||
}
|
||||
|
||||
@@ -257,12 +257,6 @@ public class ItemInfo {
|
||||
return container == CONTAINER_HOTSEAT_PREDICTION || container == CONTAINER_PREDICTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden by inherited classes to fill in {@link LauncherAtom.ItemInfo}
|
||||
*/
|
||||
public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
|
||||
*/
|
||||
|
||||
@@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
|
||||
import com.android.launcher3.AppWidgetResizeFrame;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.logger.LauncherAtom;
|
||||
import com.android.launcher3.util.ContentWriter;
|
||||
|
||||
/**
|
||||
@@ -195,13 +194,4 @@ public class LauncherAppWidgetInfo extends ItemInfo {
|
||||
public final boolean hasOptionFlag(int option) {
|
||||
return (options & option) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
|
||||
builder.setWidget(LauncherAtom.Widget.newBuilder()
|
||||
.setSpanX(spanX)
|
||||
.setSpanY(spanY)
|
||||
.setComponentName(providerName.toString())
|
||||
.setPackageName(providerName.getPackageName()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,19 @@
|
||||
|
||||
package com.android.launcher3.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.FileUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Supports various IO utility functions
|
||||
@@ -52,6 +51,9 @@ public class IOUtils {
|
||||
}
|
||||
|
||||
public static long copy(InputStream from, OutputStream to) throws IOException {
|
||||
if (Utilities.ATLEAST_Q) {
|
||||
return FileUtils.copy(from, to);
|
||||
}
|
||||
byte[] buf = new byte[BUF_SIZE];
|
||||
long total = 0;
|
||||
int r;
|
||||
@@ -62,25 +64,6 @@ public class IOUtils {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to debug binary data
|
||||
*/
|
||||
public static String createTempFile(Context context, byte[] data) {
|
||||
if (!FeatureFlags.IS_STUDIO_BUILD) {
|
||||
throw new IllegalStateException("Method only allowed in development mode");
|
||||
}
|
||||
|
||||
String name = UUID.randomUUID().toString();
|
||||
File file = new File(context.getCacheDir(), name);
|
||||
try (FileOutputStream fo = new FileOutputStream(file)) {
|
||||
fo.write(data);
|
||||
fo.flush();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
public static void closeSilently(Closeable c) {
|
||||
if (c != null) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.AutoInstallsLayout;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* Utility class to read/write a list of {@link com.android.launcher3.model.data.ItemInfo} on disk.
|
||||
* This class is not thread safe, the caller should ensure proper threading
|
||||
*/
|
||||
public class PersistedItemArray<T extends ItemInfo> {
|
||||
|
||||
private static final String TAG = "PersistedItemArray";
|
||||
|
||||
private static final String TAG_ROOT = "items";
|
||||
private static final String TAG_ENTRY = "entry";
|
||||
|
||||
private final String mFileName;
|
||||
|
||||
public PersistedItemArray(String fileName) {
|
||||
mFileName = fileName + ".xml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the provided list of items on the disk
|
||||
*/
|
||||
@WorkerThread
|
||||
public void write(Context context, List<T> items) {
|
||||
AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
|
||||
|
||||
FileOutputStream fos;
|
||||
try {
|
||||
fos = file.startWrite();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to persist items in " + mFileName, e);
|
||||
return;
|
||||
}
|
||||
|
||||
UserCache userCache = UserCache.INSTANCE.get(context);
|
||||
|
||||
try {
|
||||
XmlSerializer out = Xml.newSerializer();
|
||||
out.setOutput(fos, StandardCharsets.UTF_8.name());
|
||||
out.startDocument(null, true);
|
||||
out.startTag(null, TAG_ROOT);
|
||||
for (T item : items) {
|
||||
Intent intent = item.getIntent();
|
||||
if (intent == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out.startTag(null, TAG_ENTRY);
|
||||
out.attribute(null, Favorites.ITEM_TYPE, Integer.toString(item.itemType));
|
||||
out.attribute(null, Favorites.PROFILE_ID,
|
||||
Long.toString(userCache.getSerialNumberForUser(item.user)));
|
||||
out.attribute(null, Favorites.INTENT, intent.toUri(0));
|
||||
out.endTag(null, TAG_ENTRY);
|
||||
}
|
||||
out.endTag(null, TAG_ROOT);
|
||||
out.endDocument();
|
||||
} catch (IOException e) {
|
||||
file.failWrite(fos);
|
||||
Log.e(TAG, "Unable to persist items in " + mFileName, e);
|
||||
return;
|
||||
}
|
||||
|
||||
file.finishWrite(fos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the items from the disk
|
||||
*/
|
||||
@WorkerThread
|
||||
public List<T> read(Context context, ItemFactory<T> factory) {
|
||||
return read(context, factory, UserCache.INSTANCE.get(context)::getUserForSerialNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the items from the disk
|
||||
* @param userFn method to provide user handle for a given user serial
|
||||
*/
|
||||
@WorkerThread
|
||||
public List<T> read(Context context, ItemFactory<T> factory, LongFunction<UserHandle> userFn) {
|
||||
List<T> result = new ArrayList<>();
|
||||
AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
|
||||
|
||||
try (FileInputStream fis = file.openRead()) {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
|
||||
|
||||
AutoInstallsLayout.beginDocument(parser, TAG_ROOT);
|
||||
final int depth = parser.getDepth();
|
||||
|
||||
int type;
|
||||
while (((type = parser.next()) != XmlPullParser.END_TAG
|
||||
|| parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
|
||||
if (type != XmlPullParser.START_TAG || !TAG_ENTRY.equals(parser.getName())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
int itemType = Integer.parseInt(
|
||||
parser.getAttributeValue(null, Favorites.ITEM_TYPE));
|
||||
UserHandle user = userFn.apply(Long.parseLong(
|
||||
parser.getAttributeValue(null, Favorites.PROFILE_ID)));
|
||||
Intent intent = Intent.parseUri(
|
||||
parser.getAttributeValue(null, Favorites.INTENT), 0);
|
||||
|
||||
if (user != null && intent != null) {
|
||||
T item = factory.createInfo(itemType, user, intent);
|
||||
if (item != null) {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore this entry
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// Ignore
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Log.e(TAG, "Unable to read items in " + mFileName, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to create an ItemInfo during parsing
|
||||
*/
|
||||
public interface ItemFactory<T extends ItemInfo> {
|
||||
|
||||
/**
|
||||
* Returns an item info or null in which case the entry is ignored
|
||||
*/
|
||||
@Nullable
|
||||
T createInfo(int itemType, UserHandle user, Intent intent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user