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.content.Intent;
     20 import android.content.pm.LauncherActivityInfo;
     21 import android.os.Process;
     22 import android.os.UserHandle;
     23 import android.util.ArrayMap;
     24 import android.util.LongSparseArray;
     25 import android.util.Pair;
     26 import com.android.launcher3.AllAppsList;
     27 import com.android.launcher3.AppInfo;
     28 import com.android.launcher3.FolderInfo;
     29 import com.android.launcher3.InvariantDeviceProfile;
     30 import com.android.launcher3.ItemInfo;
     31 import com.android.launcher3.LauncherAppState;
     32 import com.android.launcher3.LauncherAppWidgetInfo;
     33 import com.android.launcher3.LauncherModel;
     34 import com.android.launcher3.LauncherModel.CallbackTask;
     35 import com.android.launcher3.LauncherModel.Callbacks;
     36 import com.android.launcher3.LauncherSettings;
     37 import com.android.launcher3.ShortcutInfo;
     38 import com.android.launcher3.Utilities;
     39 import com.android.launcher3.util.GridOccupancy;
     40 import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo;
     41 import com.android.launcher3.util.Provider;
     42 import java.util.ArrayList;
     43 import java.util.List;
     44 
     45 /**
     46  * Task to add auto-created workspace items.
     47  */
     48 public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
     49 
     50     private final Provider<List<Pair<ItemInfo, Object>>> mAppsProvider;
     51 
     52     /**
     53      * @param appsProvider items to add on the workspace
     54      */
     55     public AddWorkspaceItemsTask(Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
     56         mAppsProvider = appsProvider;
     57     }
     58 
     59     @Override
     60     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
     61         List<Pair<ItemInfo, Object>> workspaceApps = mAppsProvider.get();
     62         if (workspaceApps.isEmpty()) {
     63             return;
     64         }
     65         Context context = app.getContext();
     66 
     67         final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
     68         final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
     69         ArrayMap<UserHandle, UserFolderInfo> userFolderMap = new ArrayMap<>();
     70 
     71         // Get the list of workspace screens.  We need to append to this list and
     72         // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
     73         // called.
     74         ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
     75         synchronized(dataModel) {
     76 
     77             List<ItemInfo> filteredItems = new ArrayList<>();
     78             for (Pair<ItemInfo, Object> entry : workspaceApps) {
     79                 ItemInfo item = entry.first;
     80                 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
     81                         item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
     82                     // Short-circuit this logic if the icon exists somewhere on the workspace
     83                     if (shortcutExists(dataModel, item.getIntent(), item.user)) {
     84                         continue;
     85                     }
     86                 }
     87 
     88                 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
     89                     if (item instanceof AppInfo) {
     90                         item = ((AppInfo) item).makeShortcut();
     91                     }
     92 
     93                     if (!Process.myUserHandle().equals(item.user)) {
     94                         // Check if this belongs to a work folder.
     95                         if (!(entry.second instanceof LauncherActivityInfo)) {
     96                             continue;
     97                         }
     98 
     99                         UserFolderInfo userFolderInfo = userFolderMap.get(item.user);
    100                         if (userFolderInfo == null) {
    101                             userFolderInfo = new UserFolderInfo(context, item.user, dataModel);
    102                             userFolderMap.put(item.user, userFolderInfo);
    103                         }
    104                         item = userFolderInfo.convertToWorkspaceItem(
    105                                 (ShortcutInfo) item, (LauncherActivityInfo) entry.second);
    106                     }
    107                 }
    108                 if (item != null) {
    109                     filteredItems.add(item);
    110                 }
    111             }
    112 
    113             for (ItemInfo item : filteredItems) {
    114                 // Find appropriate space for the item.
    115                 Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens,
    116                         addedWorkspaceScreensFinal, item.spanX, item.spanY);
    117                 long screenId = coords.first;
    118                 int[] cordinates = coords.second;
    119 
    120                 ItemInfo itemInfo;
    121                 if (item instanceof ShortcutInfo || item instanceof FolderInfo ||
    122                         item instanceof LauncherAppWidgetInfo) {
    123                     itemInfo = item;
    124                 } else if (item instanceof AppInfo) {
    125                     itemInfo = ((AppInfo) item).makeShortcut();
    126                 } else {
    127                     throw new RuntimeException("Unexpected info type");
    128                 }
    129 
    130                 // Add the shortcut to the db
    131                 getModelWriter().addItemToDatabase(itemInfo,
    132                         LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
    133                         cordinates[0], cordinates[1]);
    134 
    135                 // Save the ShortcutInfo for binding in the workspace
    136                 addedItemsFinal.add(itemInfo);
    137             }
    138         }
    139 
    140         // Update the workspace screens
    141         updateScreens(context, workspaceScreens);
    142 
    143         if (!addedItemsFinal.isEmpty()) {
    144             scheduleCallbackTask(new CallbackTask() {
    145                 @Override
    146                 public void execute(Callbacks callbacks) {
    147                     final ArrayList<ItemInfo> addAnimated = new ArrayList<>();
    148                     final ArrayList<ItemInfo> addNotAnimated = new ArrayList<>();
    149                     if (!addedItemsFinal.isEmpty()) {
    150                         ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
    151                         long lastScreenId = info.screenId;
    152                         for (ItemInfo i : addedItemsFinal) {
    153                             if (i.screenId == lastScreenId) {
    154                                 addAnimated.add(i);
    155                             } else {
    156                                 addNotAnimated.add(i);
    157                             }
    158                         }
    159                     }
    160                     callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
    161                             addNotAnimated, addAnimated);
    162                 }
    163             });
    164         }
    165 
    166         for (UserFolderInfo userFolderInfo : userFolderMap.values()) {
    167             userFolderInfo.applyPendingState(getModelWriter());
    168         }
    169     }
    170 
    171     protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
    172         LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
    173     }
    174 
    175     /**
    176      * Returns true if the shortcuts already exists on the workspace. This must be called after
    177      * the workspace has been loaded. We identify a shortcut by its intent.
    178      */
    179     protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user) {
    180         final String compPkgName, intentWithPkg, intentWithoutPkg;
    181         if (intent == null) {
    182             // Skip items with null intents
    183             return true;
    184         }
    185         if (intent.getComponent() != null) {
    186             // If component is not null, an intent with null package will produce
    187             // the same result and should also be a match.
    188             compPkgName = intent.getComponent().getPackageName();
    189             if (intent.getPackage() != null) {
    190                 intentWithPkg = intent.toUri(0);
    191                 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
    192             } else {
    193                 intentWithPkg = new Intent(intent).setPackage(compPkgName).toUri(0);
    194                 intentWithoutPkg = intent.toUri(0);
    195             }
    196         } else {
    197             compPkgName = null;
    198             intentWithPkg = intent.toUri(0);
    199             intentWithoutPkg = intent.toUri(0);
    200         }
    201 
    202         boolean isLauncherAppTarget = Utilities.isLauncherAppTarget(intent);
    203         synchronized (dataModel) {
    204             for (ItemInfo item : dataModel.itemsIdMap) {
    205                 if (item instanceof ShortcutInfo) {
    206                     ShortcutInfo info = (ShortcutInfo) item;
    207                     if (item.getIntent() != null && info.user.equals(user)) {
    208                         Intent copyIntent = new Intent(item.getIntent());
    209                         copyIntent.setSourceBounds(intent.getSourceBounds());
    210                         String s = copyIntent.toUri(0);
    211                         if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
    212                             return true;
    213                         }
    214 
    215                         // checking for existing promise icon with same package name
    216                         if (isLauncherAppTarget
    217                                 && info.isPromise()
    218                                 && info.hasStatusFlag(ShortcutInfo.FLAG_AUTOINSTALL_ICON)
    219                                 && info.getTargetComponent() != null
    220                                 && compPkgName != null
    221                                 && compPkgName.equals(info.getTargetComponent().getPackageName())) {
    222                             return true;
    223                         }
    224                     }
    225                 }
    226             }
    227         }
    228         return false;
    229     }
    230 
    231     /**
    232      * Find a position on the screen for the given size or adds a new screen.
    233      * @return screenId and the coordinates for the item.
    234      */
    235     protected Pair<Long, int[]> findSpaceForItem(
    236             LauncherAppState app, BgDataModel dataModel,
    237             ArrayList<Long> workspaceScreens,
    238             ArrayList<Long> addedWorkspaceScreensFinal,
    239             int spanX, int spanY) {
    240         LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
    241 
    242         // Use sBgItemsIdMap as all the items are already loaded.
    243         synchronized (dataModel) {
    244             for (ItemInfo info : dataModel.itemsIdMap) {
    245                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    246                     ArrayList<ItemInfo> items = screenItems.get(info.screenId);
    247                     if (items == null) {
    248                         items = new ArrayList<>();
    249                         screenItems.put(info.screenId, items);
    250                     }
    251                     items.add(info);
    252                 }
    253             }
    254         }
    255 
    256         // Find appropriate space for the item.
    257         long screenId = 0;
    258         int[] cordinates = new int[2];
    259         boolean found = false;
    260 
    261         int screenCount = workspaceScreens.size();
    262         // First check the preferred screen.
    263         int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
    264         if (preferredScreenIndex < screenCount) {
    265             screenId = workspaceScreens.get(preferredScreenIndex);
    266             found = findNextAvailableIconSpaceInScreen(
    267                     app, screenItems.get(screenId), cordinates, spanX, spanY);
    268         }
    269 
    270         if (!found) {
    271             // Search on any of the screens starting from the first screen.
    272             for (int screen = 1; screen < screenCount; screen++) {
    273                 screenId = workspaceScreens.get(screen);
    274                 if (findNextAvailableIconSpaceInScreen(
    275                         app, screenItems.get(screenId), cordinates, spanX, spanY)) {
    276                     // We found a space for it
    277                     found = true;
    278                     break;
    279                 }
    280             }
    281         }
    282 
    283         if (!found) {
    284             // Still no position found. Add a new screen to the end.
    285             screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
    286                     LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
    287                     .getLong(LauncherSettings.Settings.EXTRA_VALUE);
    288 
    289             // Save the screen id for binding in the workspace
    290             workspaceScreens.add(screenId);
    291             addedWorkspaceScreensFinal.add(screenId);
    292 
    293             // If we still can't find an empty space, then God help us all!!!
    294             if (!findNextAvailableIconSpaceInScreen(
    295                     app, screenItems.get(screenId), cordinates, spanX, spanY)) {
    296                 throw new RuntimeException("Can't find space to add the item");
    297             }
    298         }
    299         return Pair.create(screenId, cordinates);
    300     }
    301 
    302     private boolean findNextAvailableIconSpaceInScreen(
    303             LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
    304             int[] xy, int spanX, int spanY) {
    305         InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
    306 
    307         GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
    308         if (occupiedPos != null) {
    309             for (ItemInfo r : occupiedPos) {
    310                 occupied.markCells(r, true);
    311             }
    312         }
    313         return occupied.findVacantCell(xy, spanX, spanY);
    314     }
    315 
    316 }
    317