blob: 2522a498e6145d188f7f462b71c1d6c2b889563e [file] [log] [blame]
/*
* Copyright (C) 2016 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 static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.MutableInt;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.PromiseAppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.widget.WidgetListRowEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* All the data stored in-memory and managed by the LauncherModel
*/
public class BgDataModel {
private static final String TAG = "BgDataModel";
/**
* Map of all the ItemInfos (shortcuts, folders, and widgets) created by
* LauncherModel to their ids
*/
public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>();
/**
* List of all the folders and shortcuts directly on the home screen (no widgets
* or shortcuts within folders).
*/
public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
/**
* All LauncherAppWidgetInfo created by LauncherModel.
*/
public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
/**
* Map of id to FolderInfos of all the folders created by LauncherModel
*/
public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
/**
* Map of ShortcutKey to the number of times it is pinned.
*/
public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
/**
* List of all cached predicted items visible on home screen
*/
public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
/**
* True if the launcher has permission to access deep shortcuts.
*/
public boolean hasShortcutHostPermission;
/**
* Maps all launcher activities to counts of their shortcuts.
*/
public final HashMap<ComponentKey, Integer> deepShortcutMap = new HashMap<>();
/**
* Entire list of widgets.
*/
public final WidgetsModel widgetsModel = new WidgetsModel();
/**
* Id when the model was last bound
*/
public int lastBindId = 0;
/**
* Clears all the data
*/
public synchronized void clear() {
workspaceItems.clear();
appWidgets.clear();
folders.clear();
itemsIdMap.clear();
pinnedShortcutCounts.clear();
deepShortcutMap.clear();
}
/**
* Creates an array of valid workspace screens based on current items in the model.
*/
public synchronized IntArray collectWorkspaceScreens() {
IntSet screenSet = new IntSet();
for (ItemInfo item: itemsIdMap) {
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
screenSet.add(item.screenId);
}
}
if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
screenSet.add(Workspace.FIRST_SCREEN_ID);
}
return screenSet.getArray();
}
public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
String[] args) {
writer.println(prefix + "Data Model:");
writer.println(prefix + " ---- workspace items ");
for (int i = 0; i < workspaceItems.size(); i++) {
writer.println(prefix + '\t' + workspaceItems.get(i).toString());
}
writer.println(prefix + " ---- appwidget items ");
for (int i = 0; i < appWidgets.size(); i++) {
writer.println(prefix + '\t' + appWidgets.get(i).toString());
}
writer.println(prefix + " ---- folder items ");
for (int i = 0; i< folders.size(); i++) {
writer.println(prefix + '\t' + folders.valueAt(i).toString());
}
writer.println(prefix + " ---- items id map ");
for (int i = 0; i< itemsIdMap.size(); i++) {
writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
}
if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
writer.println(prefix + "shortcut counts ");
for (Integer count : deepShortcutMap.values()) {
writer.print(count + ", ");
}
writer.println();
}
}
public synchronized void removeItem(Context context, ItemInfo... items) {
removeItem(context, Arrays.asList(items));
}
public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
for (ItemInfo item : items) {
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
folders.remove(item.id);
if (FeatureFlags.IS_STUDIO_BUILD) {
for (ItemInfo info : itemsIdMap) {
if (info.container == item.id) {
// We are deleting a folder which still contains items that
// think they are contained by that folder.
String msg = "deleting a folder (" + item + ") which still " +
"contains items (" + info + ")";
Log.e(TAG, msg);
}
}
}
workspaceItems.remove(item);
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
// Decrement pinned shortcut count
ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
if ((count == null || --count.value == 0)
&& !InstallShortcutReceiver.getPendingShortcuts(context)
.contains(pinnedShortcut)) {
unpinShortcut(context, pinnedShortcut);
}
// Fall through.
}
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
workspaceItems.remove(item);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
appWidgets.remove(item);
break;
}
itemsIdMap.remove(item.id);
}
}
public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
itemsIdMap.put(item.id, item);
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
folders.put(item.id, (FolderInfo) item);
workspaceItems.add(item);
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
// Increment the count for the given shortcut
ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
if (count == null) {
count = new MutableInt(1);
pinnedShortcutCounts.put(pinnedShortcut, count);
} else {
count.value++;
}
// Since this is a new item, pin the shortcut in the system server.
if (newItem && count.value == 1) {
updatePinnedShortcuts(context, pinnedShortcut, List::add);
}
// Fall through
}
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
workspaceItems.add(item);
} else {
if (newItem) {
if (!folders.containsKey(item.container)) {
// Adding an item to a folder that doesn't exist.
String msg = "adding item: " + item + " to a folder that " +
" doesn't exist";
Log.e(TAG, msg);
}
} else {
findOrMakeFolder(item.container).add((WorkspaceItemInfo) item, false);
}
}
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
appWidgets.add((LauncherAppWidgetInfo) item);
break;
}
}
/**
* Removes the given shortcut from the current list of pinned shortcuts.
* (Runs on background thread)
*/
public void unpinShortcut(Context context, ShortcutKey key) {
updatePinnedShortcuts(context, key, List::remove);
}
private void updatePinnedShortcuts(Context context, ShortcutKey key,
BiConsumer<List<String>, String> idOp) {
if (GO_DISABLE_WIDGETS) {
return;
}
String packageName = key.componentName.getPackageName();
String id = key.getId();
UserHandle user = key.user;
List<String> pinnedIds = new ShortcutRequest(context, user)
.forPackage(packageName)
.query(PINNED)
.stream()
.map(ShortcutInfo::getId)
.collect(Collectors.toCollection(ArrayList::new));
idOp.accept(pinnedIds, id);
try {
context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user);
} catch (SecurityException | IllegalStateException e) {
Log.w(TAG, "Failed to pin shortcut", e);
}
}
/**
* Return an existing FolderInfo object if we have encountered this ID previously,
* or make a new one.
*/
public synchronized FolderInfo findOrMakeFolder(int id) {
// See if a placeholder was created for us already
FolderInfo folderInfo = folders.get(id);
if (folderInfo == null) {
// No placeholder -- create a new instance
folderInfo = new FolderInfo();
folders.put(id, folderInfo);
}
return folderInfo;
}
/**
* Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
*/
public synchronized void updateDeepShortcutCounts(
String packageName, UserHandle user, List<ShortcutInfo> shortcuts) {
if (packageName != null) {
Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
while (keysIter.hasNext()) {
ComponentKey next = keysIter.next();
if (next.componentName.getPackageName().equals(packageName)
&& next.user.equals(user)) {
keysIter.remove();
}
}
}
// Now add the new shortcuts to the map.
for (ShortcutInfo shortcut : shortcuts) {
boolean shouldShowInContainer = shortcut.isEnabled()
&& (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
&& shortcut.getActivity() != null;
if (shouldShowInContainer) {
ComponentKey targetComponent
= new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
Integer previousCount = deepShortcutMap.get(targetComponent);
deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1);
}
}
}
public interface Callbacks {
/**
* Returns the page number to bind first, synchronously if possible or -1
*/
int getPageToBindSynchronously();
void clearPendingBinds();
void startBinding();
void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
void bindScreens(IntArray orderedScreenIds);
void finishFirstPageBind(ViewOnDrawExecutor executor);
void finishBindingItems(int pageBoundFirst);
void preAddApps();
void bindAppsAdded(IntArray newScreens,
ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
void bindPromiseAppProgressUpdated(PromiseAppInfo app);
void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
void bindRestoreItemsChange(HashSet<ItemInfo> updates);
void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
void onPageBoundSynchronously(int page);
void executeOnNextDraw(ViewOnDrawExecutor executor);
void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
void bindAllApplications(AppInfo[] apps);
/**
* Binds predicted appInfos at at available prediction slots.
*/
void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks);
}
}