Home | History | Annotate | Download | only in model
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.launcher3.model;
     17 
     18 import android.content.Context;
     19 import android.os.UserHandle;
     20 import android.text.TextUtils;
     21 import android.util.Log;
     22 import android.util.MutableInt;
     23 
     24 import com.android.launcher3.FolderInfo;
     25 import com.android.launcher3.InstallShortcutReceiver;
     26 import com.android.launcher3.ItemInfo;
     27 import com.android.launcher3.LauncherAppWidgetInfo;
     28 import com.android.launcher3.LauncherSettings;
     29 import com.android.launcher3.ShortcutInfo;
     30 import com.android.launcher3.config.FeatureFlags;
     31 import com.android.launcher3.logging.DumpTargetWrapper;
     32 import com.android.launcher3.model.nano.LauncherDumpProto;
     33 import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
     34 import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
     35 import com.android.launcher3.shortcuts.DeepShortcutManager;
     36 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
     37 import com.android.launcher3.shortcuts.ShortcutKey;
     38 import com.android.launcher3.util.ComponentKey;
     39 import com.android.launcher3.util.LongArrayMap;
     40 import com.android.launcher3.util.MultiHashMap;
     41 import com.google.protobuf.nano.MessageNano;
     42 
     43 import java.io.FileDescriptor;
     44 import java.io.FileOutputStream;
     45 import java.io.IOException;
     46 import java.io.PrintWriter;
     47 import java.util.ArrayList;
     48 import java.util.Arrays;
     49 import java.util.HashMap;
     50 import java.util.Iterator;
     51 import java.util.List;
     52 import java.util.Map;
     53 
     54 /**
     55  * All the data stored in-memory and managed by the LauncherModel
     56  */
     57 public class BgDataModel {
     58 
     59     private static final String TAG = "BgDataModel";
     60 
     61     /**
     62      * Map of all the ItemInfos (shortcuts, folders, and widgets) created by
     63      * LauncherModel to their ids
     64      */
     65     public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>();
     66 
     67     /**
     68      * List of all the folders and shortcuts directly on the home screen (no widgets
     69      * or shortcuts within folders).
     70      */
     71     public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
     72 
     73     /**
     74      * All LauncherAppWidgetInfo created by LauncherModel.
     75      */
     76     public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
     77 
     78     /**
     79      * Map of id to FolderInfos of all the folders created by LauncherModel
     80      */
     81     public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>();
     82 
     83     /**
     84      * Ordered list of workspace screens ids.
     85      */
     86     public final ArrayList<Long> workspaceScreens = new ArrayList<>();
     87 
     88     /**
     89      * Map of ShortcutKey to the number of times it is pinned.
     90      */
     91     public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
     92 
     93     /**
     94      * True if the launcher has permission to access deep shortcuts.
     95      */
     96     public boolean hasShortcutHostPermission;
     97 
     98     /**
     99      * Maps all launcher activities to the id's of their shortcuts (if they have any).
    100      */
    101     public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>();
    102 
    103     /**
    104      * Entire list of widgets.
    105      */
    106     public final WidgetsModel widgetsModel = new WidgetsModel();
    107 
    108     /**
    109      * Id when the model was last bound
    110      */
    111     public int lastBindId = 0;
    112 
    113     /**
    114      * Clears all the data
    115      */
    116     public synchronized void clear() {
    117         workspaceItems.clear();
    118         appWidgets.clear();
    119         folders.clear();
    120         itemsIdMap.clear();
    121         workspaceScreens.clear();
    122         pinnedShortcutCounts.clear();
    123         deepShortcutMap.clear();
    124     }
    125 
    126     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
    127             String[] args) {
    128         if (Arrays.asList(args).contains("--proto")) {
    129             dumpProto(prefix, fd, writer, args);
    130             return;
    131         }
    132         writer.println(prefix + "Data Model:");
    133         writer.print(prefix + " ---- workspace screens: ");
    134         for (int i = 0; i < workspaceScreens.size(); i++) {
    135             writer.print(" " + workspaceScreens.get(i).toString());
    136         }
    137         writer.println();
    138         writer.println(prefix + " ---- workspace items ");
    139         for (int i = 0; i < workspaceItems.size(); i++) {
    140             writer.println(prefix + '\t' + workspaceItems.get(i).toString());
    141         }
    142         writer.println(prefix + " ---- appwidget items ");
    143         for (int i = 0; i < appWidgets.size(); i++) {
    144             writer.println(prefix + '\t' + appWidgets.get(i).toString());
    145         }
    146         writer.println(prefix + " ---- folder items ");
    147         for (int i = 0; i< folders.size(); i++) {
    148             writer.println(prefix + '\t' + folders.valueAt(i).toString());
    149         }
    150         writer.println(prefix + " ---- items id map ");
    151         for (int i = 0; i< itemsIdMap.size(); i++) {
    152             writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
    153         }
    154 
    155         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
    156             writer.println(prefix + "shortcuts");
    157             for (ArrayList<String> map : deepShortcutMap.values()) {
    158                 writer.print(prefix + "  ");
    159                 for (String str : map) {
    160                     writer.print(str + ", ");
    161                 }
    162                 writer.println();
    163             }
    164         }
    165     }
    166 
    167     private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer,
    168             String[] args) {
    169 
    170         // Add top parent nodes. (L1)
    171         DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
    172         LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>();
    173         for (int i = 0; i < workspaceScreens.size(); i++) {
    174             workspaces.put(workspaceScreens.get(i),
    175                     new DumpTargetWrapper(ContainerType.WORKSPACE, i));
    176         }
    177         DumpTargetWrapper dtw;
    178         // Add non leaf / non top nodes (L2)
    179         for (int i = 0; i < folders.size(); i++) {
    180             FolderInfo fInfo = folders.valueAt(i);
    181             dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size());
    182             dtw.writeToDumpTarget(fInfo);
    183             for(ShortcutInfo sInfo: fInfo.contents) {
    184                 DumpTargetWrapper child = new DumpTargetWrapper(sInfo);
    185                 child.writeToDumpTarget(sInfo);
    186                 dtw.add(child);
    187             }
    188             if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    189                 hotseat.add(dtw);
    190             } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    191                 workspaces.get(fInfo.screenId).add(dtw);
    192             }
    193         }
    194         // Add leaf nodes (L3): *Info
    195         for (int i = 0; i < workspaceItems.size(); i++) {
    196             ItemInfo info = workspaceItems.get(i);
    197             if (info instanceof FolderInfo) {
    198                 continue;
    199             }
    200             dtw = new DumpTargetWrapper(info);
    201             dtw.writeToDumpTarget(info);
    202             if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    203                 hotseat.add(dtw);
    204             } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    205                 workspaces.get(info.screenId).add(dtw);
    206             }
    207         }
    208         for (int i = 0; i < appWidgets.size(); i++) {
    209             ItemInfo info = appWidgets.get(i);
    210             dtw = new DumpTargetWrapper(info);
    211             dtw.writeToDumpTarget(info);
    212             if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    213                 hotseat.add(dtw);
    214             } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    215                 workspaces.get(info.screenId).add(dtw);
    216             }
    217         }
    218 
    219 
    220         // Traverse target wrapper
    221         ArrayList<DumpTarget> targetList = new ArrayList<>();
    222         targetList.addAll(hotseat.getFlattenedList());
    223         for (int i = 0; i < workspaces.size(); i++) {
    224             targetList.addAll(workspaces.valueAt(i).getFlattenedList());
    225         }
    226 
    227         if (Arrays.asList(args).contains("--debug")) {
    228             for (int i = 0; i < targetList.size(); i++) {
    229                 writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
    230             }
    231             return;
    232         } else {
    233             LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression();
    234             proto.targets = new DumpTarget[targetList.size()];
    235             for (int i = 0; i < targetList.size(); i++) {
    236                 proto.targets[i] = targetList.get(i);
    237             }
    238             FileOutputStream fos = new FileOutputStream(fd);
    239             try {
    240 
    241                 fos.write(MessageNano.toByteArray(proto));
    242                 Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes");
    243             } catch (IOException e) {
    244                 Log.e(TAG, "Exception writing dumpsys --proto", e);
    245             }
    246         }
    247     }
    248 
    249     public synchronized void removeItem(Context context, ItemInfo... items) {
    250         removeItem(context, Arrays.asList(items));
    251     }
    252 
    253     public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
    254         for (ItemInfo item : items) {
    255             switch (item.itemType) {
    256                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
    257                     folders.remove(item.id);
    258                     if (FeatureFlags.IS_DOGFOOD_BUILD) {
    259                         for (ItemInfo info : itemsIdMap) {
    260                             if (info.container == item.id) {
    261                                 // We are deleting a folder which still contains items that
    262                                 // think they are contained by that folder.
    263                                 String msg = "deleting a folder (" + item + ") which still " +
    264                                         "contains items (" + info + ")";
    265                                 Log.e(TAG, msg);
    266                             }
    267                         }
    268                     }
    269                     workspaceItems.remove(item);
    270                     break;
    271                 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
    272                     // Decrement pinned shortcut count
    273                     ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
    274                     MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
    275                     if ((count == null || --count.value == 0)
    276                             && !InstallShortcutReceiver.getPendingShortcuts(context)
    277                                 .contains(pinnedShortcut)) {
    278                         DeepShortcutManager.getInstance(context).unpinShortcut(pinnedShortcut);
    279                     }
    280                     // Fall through.
    281                 }
    282                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
    283                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
    284                     workspaceItems.remove(item);
    285                     break;
    286                 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
    287                 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
    288                     appWidgets.remove(item);
    289                     break;
    290             }
    291             itemsIdMap.remove(item.id);
    292         }
    293     }
    294 
    295     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
    296         itemsIdMap.put(item.id, item);
    297         switch (item.itemType) {
    298             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
    299                 folders.put(item.id, (FolderInfo) item);
    300                 workspaceItems.add(item);
    301                 break;
    302             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
    303                 // Increment the count for the given shortcut
    304                 ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
    305                 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
    306                 if (count == null) {
    307                     count = new MutableInt(1);
    308                     pinnedShortcutCounts.put(pinnedShortcut, count);
    309                 } else {
    310                     count.value++;
    311                 }
    312 
    313                 // Since this is a new item, pin the shortcut in the system server.
    314                 if (newItem && count.value == 1) {
    315                     DeepShortcutManager.getInstance(context).pinShortcut(pinnedShortcut);
    316                 }
    317                 // Fall through
    318             }
    319             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
    320             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
    321                 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
    322                         item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    323                     workspaceItems.add(item);
    324                 } else {
    325                     if (newItem) {
    326                         if (!folders.containsKey(item.container)) {
    327                             // Adding an item to a folder that doesn't exist.
    328                             String msg = "adding item: " + item + " to a folder that " +
    329                                     " doesn't exist";
    330                             Log.e(TAG, msg);
    331                         }
    332                     } else {
    333                         findOrMakeFolder(item.container).add((ShortcutInfo) item, false);
    334                     }
    335 
    336                 }
    337                 break;
    338             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
    339             case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
    340                 appWidgets.add((LauncherAppWidgetInfo) item);
    341                 break;
    342         }
    343     }
    344 
    345     /**
    346      * Return an existing FolderInfo object if we have encountered this ID previously,
    347      * or make a new one.
    348      */
    349     public synchronized FolderInfo findOrMakeFolder(long id) {
    350         // See if a placeholder was created for us already
    351         FolderInfo folderInfo = folders.get(id);
    352         if (folderInfo == null) {
    353             // No placeholder -- create a new instance
    354             folderInfo = new FolderInfo();
    355             folders.put(id, folderInfo);
    356         }
    357         return folderInfo;
    358     }
    359 
    360     /**
    361      * Clear all the deep shortcuts for the given package, and re-add the new shortcuts.
    362      */
    363     public synchronized void updateDeepShortcutMap(
    364             String packageName, UserHandle user, List<ShortcutInfoCompat> shortcuts) {
    365         if (packageName != null) {
    366             Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
    367             while (keysIter.hasNext()) {
    368                 ComponentKey next = keysIter.next();
    369                 if (next.componentName.getPackageName().equals(packageName)
    370                         && next.user.equals(user)) {
    371                     keysIter.remove();
    372                 }
    373             }
    374         }
    375 
    376         // Now add the new shortcuts to the map.
    377         for (ShortcutInfoCompat shortcut : shortcuts) {
    378             boolean shouldShowInContainer = shortcut.isEnabled()
    379                     && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
    380             if (shouldShowInContainer) {
    381                 ComponentKey targetComponent
    382                         = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
    383                 deepShortcutMap.addToList(targetComponent, shortcut.getId());
    384             }
    385         }
    386     }
    387 }
    388