| /* |
| * 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 android.content.Context; |
| import android.os.UserHandle; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.util.MutableInt; |
| |
| import com.android.launcher3.FolderInfo; |
| import com.android.launcher3.InstallShortcutReceiver; |
| import com.android.launcher3.ItemInfo; |
| import com.android.launcher3.LauncherAppWidgetInfo; |
| import com.android.launcher3.LauncherSettings; |
| import com.android.launcher3.ShortcutInfo; |
| import com.android.launcher3.config.FeatureFlags; |
| import com.android.launcher3.logging.DumpTargetWrapper; |
| import com.android.launcher3.model.nano.LauncherDumpProto; |
| import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType; |
| import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget; |
| import com.android.launcher3.shortcuts.DeepShortcutManager; |
| import com.android.launcher3.shortcuts.ShortcutInfoCompat; |
| import com.android.launcher3.shortcuts.ShortcutKey; |
| import com.android.launcher3.util.ComponentKey; |
| import com.android.launcher3.util.LongArrayMap; |
| import com.android.launcher3.util.MultiHashMap; |
| import com.google.protobuf.nano.MessageNano; |
| |
| import java.io.FileDescriptor; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * 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 LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>(); |
| |
| /** |
| * 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 LongArrayMap<FolderInfo> folders = new LongArrayMap<>(); |
| |
| /** |
| * Ordered list of workspace screens ids. |
| */ |
| public final ArrayList<Long> workspaceScreens = new ArrayList<>(); |
| |
| /** |
| * Map of ShortcutKey to the number of times it is pinned. |
| */ |
| public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>(); |
| |
| /** |
| * True if the launcher has permission to access deep shortcuts. |
| */ |
| public boolean hasShortcutHostPermission; |
| |
| /** |
| * Maps all launcher activities to the id's of their shortcuts (if they have any). |
| */ |
| public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>(); |
| |
| /** |
| * Entire list of widgets. |
| */ |
| public final WidgetsModel widgetsModel = new WidgetsModel(); |
| |
| /** |
| * Clears all the data |
| */ |
| public synchronized void clear() { |
| workspaceItems.clear(); |
| appWidgets.clear(); |
| folders.clear(); |
| itemsIdMap.clear(); |
| workspaceScreens.clear(); |
| pinnedShortcutCounts.clear(); |
| deepShortcutMap.clear(); |
| } |
| |
| public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, |
| String[] args) { |
| if (Arrays.asList(args).contains("--proto")) { |
| dumpProto(prefix, fd, writer, args); |
| return; |
| } |
| writer.println(prefix + "Data Model:"); |
| writer.print(prefix + " ---- workspace screens: "); |
| for (int i = 0; i < workspaceScreens.size(); i++) { |
| writer.print(" " + workspaceScreens.get(i).toString()); |
| } |
| writer.println(); |
| 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 + "shortcuts"); |
| for (ArrayList<String> map : deepShortcutMap.values()) { |
| writer.print(prefix + " "); |
| for (String str : map) { |
| writer.print(str + ", "); |
| } |
| writer.println(); |
| } |
| } |
| } |
| |
| private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer, |
| String[] args) { |
| |
| // Add top parent nodes. (L1) |
| DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0); |
| LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>(); |
| for (int i = 0; i < workspaceScreens.size(); i++) { |
| workspaces.put(workspaceScreens.get(i), |
| new DumpTargetWrapper(ContainerType.WORKSPACE, i)); |
| } |
| DumpTargetWrapper dtw; |
| // Add non leaf / non top nodes (L2) |
| for (int i = 0; i < folders.size(); i++) { |
| FolderInfo fInfo = folders.valueAt(i); |
| dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size()); |
| dtw.writeToDumpTarget(fInfo); |
| for(ShortcutInfo sInfo: fInfo.contents) { |
| DumpTargetWrapper child = new DumpTargetWrapper(sInfo); |
| child.writeToDumpTarget(sInfo); |
| dtw.add(child); |
| } |
| if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { |
| hotseat.add(dtw); |
| } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { |
| workspaces.get(fInfo.screenId).add(dtw); |
| } |
| } |
| // Add leaf nodes (L3): *Info |
| for (int i = 0; i < workspaceItems.size(); i++) { |
| ItemInfo info = workspaceItems.get(i); |
| if (info instanceof FolderInfo) { |
| continue; |
| } |
| dtw = new DumpTargetWrapper(info); |
| dtw.writeToDumpTarget(info); |
| if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { |
| hotseat.add(dtw); |
| } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { |
| workspaces.get(info.screenId).add(dtw); |
| } |
| } |
| for (int i = 0; i < appWidgets.size(); i++) { |
| ItemInfo info = appWidgets.get(i); |
| dtw = new DumpTargetWrapper(info); |
| dtw.writeToDumpTarget(info); |
| if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { |
| hotseat.add(dtw); |
| } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { |
| workspaces.get(info.screenId).add(dtw); |
| } |
| } |
| |
| |
| // Traverse target wrapper |
| ArrayList<DumpTarget> targetList = new ArrayList<>(); |
| targetList.addAll(hotseat.getFlattenedList()); |
| for (int i = 0; i < workspaces.size(); i++) { |
| targetList.addAll(workspaces.valueAt(i).getFlattenedList()); |
| } |
| |
| if (Arrays.asList(args).contains("--debug")) { |
| for (int i = 0; i < targetList.size(); i++) { |
| writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i))); |
| } |
| return; |
| } else { |
| LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression(); |
| proto.targets = new DumpTarget[targetList.size()]; |
| for (int i = 0; i < targetList.size(); i++) { |
| proto.targets[i] = targetList.get(i); |
| } |
| FileOutputStream fos = new FileOutputStream(fd); |
| try { |
| |
| fos.write(MessageNano.toByteArray(proto)); |
| Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes"); |
| } catch (IOException e) { |
| Log.e(TAG, "Exception writing dumpsys --proto", e); |
| } |
| } |
| } |
| |
| 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_DOGFOOD_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)) { |
| DeepShortcutManager.getInstance(context).unpinShortcut(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) { |
| DeepShortcutManager.getInstance(context).pinShortcut(pinnedShortcut); |
| } |
| // 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((ShortcutInfo) item, false); |
| } |
| |
| } |
| break; |
| case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: |
| case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: |
| appWidgets.add((LauncherAppWidgetInfo) item); |
| break; |
| } |
| } |
| |
| /** |
| * Return an existing FolderInfo object if we have encountered this ID previously, |
| * or make a new one. |
| */ |
| public synchronized FolderInfo findOrMakeFolder(long 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 shortcuts for the given package, and re-add the new shortcuts. |
| */ |
| public synchronized void updateDeepShortcutMap( |
| String packageName, UserHandle user, List<ShortcutInfoCompat> 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 (ShortcutInfoCompat shortcut : shortcuts) { |
| boolean shouldShowInContainer = shortcut.isEnabled() |
| && (shortcut.isDeclaredInManifest() || shortcut.isDynamic()); |
| if (shouldShowInContainer) { |
| ComponentKey targetComponent |
| = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle()); |
| deepShortcutMap.addToList(targetComponent, shortcut.getId()); |
| } |
| } |
| } |
| } |