Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2015 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 
     17 package com.android.launcher3.util;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.content.SharedPreferences;
     22 import android.content.pm.PackageInfo;
     23 import android.content.pm.PackageManager;
     24 import android.content.pm.PackageManager.NameNotFoundException;
     25 import android.os.Build;
     26 import android.util.Log;
     27 
     28 import com.android.launcher3.FolderInfo;
     29 import com.android.launcher3.ItemInfo;
     30 import com.android.launcher3.LauncherAppState;
     31 import com.android.launcher3.LauncherFiles;
     32 import com.android.launcher3.LauncherModel;
     33 import com.android.launcher3.MainThreadExecutor;
     34 import com.android.launcher3.R;
     35 import com.android.launcher3.ShortcutInfo;
     36 import com.android.launcher3.Utilities;
     37 import com.android.launcher3.compat.LauncherActivityInfoCompat;
     38 import com.android.launcher3.compat.LauncherAppsCompat;
     39 import com.android.launcher3.compat.UserHandleCompat;
     40 import com.android.launcher3.compat.UserManagerCompat;
     41 
     42 import java.util.ArrayList;
     43 import java.util.Collections;
     44 import java.util.Comparator;
     45 import java.util.HashMap;
     46 import java.util.HashSet;
     47 import java.util.List;
     48 import java.util.Set;
     49 
     50 /**
     51  * Handles addition of app shortcuts for managed profiles.
     52  * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
     53  */
     54 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     55 public class ManagedProfileHeuristic {
     56 
     57     private static final String TAG = "ManagedProfileHeuristic";
     58 
     59     /**
     60      * Maintain a set of packages installed per user.
     61      */
     62     private static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_";
     63 
     64     private static final String USER_FOLDER_ID_PREFIX = "user_folder_";
     65 
     66     /**
     67      * Duration (in milliseconds) for which app shortcuts will be added to work folder.
     68      */
     69     private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
     70 
     71     public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
     72         if (Utilities.ATLEAST_LOLLIPOP && !UserHandleCompat.myUserHandle().equals(user)) {
     73             return new ManagedProfileHeuristic(context, user);
     74         }
     75         return null;
     76     }
     77 
     78     private final Context mContext;
     79     private final UserHandleCompat mUser;
     80     private final LauncherModel mModel;
     81 
     82     private final SharedPreferences mPrefs;
     83     private final long mUserSerial;
     84     private final long mUserCreationTime;
     85     private final String mPackageSetKey;
     86 
     87     private ArrayList<ShortcutInfo> mHomescreenApps;
     88     private ArrayList<ShortcutInfo> mWorkFolderApps;
     89     private HashMap<ShortcutInfo, Long> mShortcutToInstallTimeMap;
     90 
     91     private ManagedProfileHeuristic(Context context, UserHandleCompat user) {
     92         mContext = context;
     93         mUser = user;
     94         mModel = LauncherAppState.getInstance().getModel();
     95 
     96         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
     97         mUserSerial = userManager.getSerialNumberForUser(user);
     98         mUserCreationTime = userManager.getUserCreationTime(user);
     99         mPackageSetKey = INSTALLED_PACKAGES_PREFIX + mUserSerial;
    100 
    101         mPrefs = mContext.getSharedPreferences(LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
    102                 Context.MODE_PRIVATE);
    103     }
    104 
    105     private void initVars() {
    106         mHomescreenApps = new ArrayList<>();
    107         mWorkFolderApps = new ArrayList<>();
    108         mShortcutToInstallTimeMap = new HashMap<>();
    109     }
    110 
    111     /**
    112      * Checks the list of user apps and adds icons for newly installed apps on the homescreen or
    113      * workfolder.
    114      */
    115     public void processUserApps(List<LauncherActivityInfoCompat> apps) {
    116         initVars();
    117 
    118         HashSet<String> packageSet = new HashSet<>();
    119         final boolean userAppsExisted = getUserApps(packageSet);
    120 
    121         boolean newPackageAdded = false;
    122         for (LauncherActivityInfoCompat info : apps) {
    123             String packageName = info.getComponentName().getPackageName();
    124             if (!packageSet.contains(packageName)) {
    125                 packageSet.add(packageName);
    126                 newPackageAdded = true;
    127                 markForAddition(info, info.getFirstInstallTime());
    128             }
    129         }
    130 
    131         if (newPackageAdded) {
    132             mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
    133             // Do not add shortcuts on the homescreen for the first time. This prevents the launcher
    134             // getting filled with the managed user apps, when it start with a fresh DB (or after
    135             // a very long time).
    136             finalizeAdditions(userAppsExisted);
    137         }
    138     }
    139 
    140     private void markForAddition(LauncherActivityInfoCompat info, long installTime) {
    141         ArrayList<ShortcutInfo> targetList =
    142                 (installTime <= mUserCreationTime + AUTO_ADD_TO_FOLDER_DURATION) ?
    143                         mWorkFolderApps : mHomescreenApps;
    144         ShortcutInfo si = ShortcutInfo.fromActivityInfo(info, mContext);
    145         mShortcutToInstallTimeMap.put(si, installTime);
    146         targetList.add(si);
    147     }
    148 
    149     private void sortList(ArrayList<ShortcutInfo> infos) {
    150         Collections.sort(infos, new Comparator<ShortcutInfo>() {
    151 
    152             @Override
    153             public int compare(ShortcutInfo lhs, ShortcutInfo rhs) {
    154                 Long lhsTime = mShortcutToInstallTimeMap.get(lhs);
    155                 Long rhsTime = mShortcutToInstallTimeMap.get(rhs);
    156                 return Utilities.longCompare(lhsTime == null ? 0 : lhsTime,
    157                         rhsTime == null ? 0 : rhsTime);
    158             }
    159         });
    160     }
    161 
    162     /**
    163      * Adds and binds shortcuts marked to be added to the work folder.
    164      */
    165     private void finalizeWorkFolder() {
    166         if (mWorkFolderApps.isEmpty()) {
    167             return;
    168         }
    169         sortList(mWorkFolderApps);
    170 
    171         // Try to get a work folder.
    172         String folderIdKey = USER_FOLDER_ID_PREFIX + mUserSerial;
    173         if (mPrefs.contains(folderIdKey)) {
    174             long folderId = mPrefs.getLong(folderIdKey, 0);
    175             final FolderInfo workFolder = mModel.findFolderById(folderId);
    176 
    177             if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) {
    178                 // Could not get a work folder. Add all the icons to homescreen.
    179                 mHomescreenApps.addAll(mWorkFolderApps);
    180                 return;
    181             }
    182             saveWorkFolderShortcuts(folderId, workFolder.contents.size());
    183 
    184             final ArrayList<ShortcutInfo> shortcuts = mWorkFolderApps;
    185             // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
    186             new MainThreadExecutor().execute(new Runnable() {
    187 
    188                 @Override
    189                 public void run() {
    190                     for (ShortcutInfo info : shortcuts) {
    191                         workFolder.add(info);
    192                     }
    193                 }
    194             });
    195         } else {
    196             // Create a new folder.
    197             final FolderInfo workFolder = new FolderInfo();
    198             workFolder.title = mContext.getText(R.string.work_folder_name);
    199             workFolder.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null);
    200 
    201             // Add all shortcuts before adding it to the UI, as an empty folder might get deleted.
    202             for (ShortcutInfo info : mWorkFolderApps) {
    203                 workFolder.add(info);
    204             }
    205 
    206             // Add the item to home screen and DB. This also generates an item id synchronously.
    207             ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
    208             itemList.add(workFolder);
    209             mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
    210             mPrefs.edit().putLong(USER_FOLDER_ID_PREFIX + mUserSerial, workFolder.id).apply();
    211 
    212             saveWorkFolderShortcuts(workFolder.id, 0);
    213         }
    214     }
    215 
    216     /**
    217      * Add work folder shortcuts to the DB.
    218      */
    219     private void saveWorkFolderShortcuts(long workFolderId, int startingRank) {
    220         for (ItemInfo info : mWorkFolderApps) {
    221             info.rank = startingRank++;
    222             LauncherModel.addItemToDatabase(mContext, info, workFolderId, 0, 0, 0);
    223         }
    224     }
    225 
    226     /**
    227      * Adds and binds all shortcuts marked for addition.
    228      */
    229     private void finalizeAdditions(boolean addHomeScreenShortcuts) {
    230         finalizeWorkFolder();
    231 
    232         if (addHomeScreenShortcuts && !mHomescreenApps.isEmpty()) {
    233             sortList(mHomescreenApps);
    234             mModel.addAndBindAddedWorkspaceItems(mContext, mHomescreenApps);
    235         }
    236     }
    237 
    238     /**
    239      * Updates the list of installed apps and adds any new icons on homescreen or work folder.
    240      */
    241     public void processPackageAdd(String[] packages) {
    242         initVars();
    243         HashSet<String> packageSet = new HashSet<>();
    244         final boolean userAppsExisted = getUserApps(packageSet);
    245 
    246         boolean newPackageAdded = false;
    247         long installTime = System.currentTimeMillis();
    248         LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
    249 
    250         for (String packageName : packages) {
    251             if (!packageSet.contains(packageName)) {
    252                 packageSet.add(packageName);
    253                 newPackageAdded = true;
    254 
    255                 List<LauncherActivityInfoCompat> activities =
    256                         launcherApps.getActivityList(packageName, mUser);
    257                 if (!activities.isEmpty()) {
    258                     markForAddition(activities.get(0), installTime);
    259                 }
    260             }
    261         }
    262 
    263         if (newPackageAdded) {
    264             mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
    265             finalizeAdditions(userAppsExisted);
    266         }
    267     }
    268 
    269     /**
    270      * Updates the list of installed packages for the user.
    271      */
    272     public void processPackageRemoved(String[] packages) {
    273         HashSet<String> packageSet = new HashSet<String>();
    274         getUserApps(packageSet);
    275         boolean packageRemoved = false;
    276 
    277         for (String packageName : packages) {
    278             if (packageSet.remove(packageName)) {
    279                 packageRemoved = true;
    280             }
    281         }
    282 
    283         if (packageRemoved) {
    284             mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
    285         }
    286     }
    287 
    288     /**
    289      * Reads the list of user apps which have already been processed.
    290      * @return false if the list didn't exist, true otherwise
    291      */
    292     private boolean getUserApps(HashSet<String> outExistingApps) {
    293         Set<String> userApps = mPrefs.getStringSet(mPackageSetKey, null);
    294         if (userApps == null) {
    295             return false;
    296         } else {
    297             outExistingApps.addAll(userApps);
    298             return true;
    299         }
    300     }
    301 
    302     /**
    303      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
    304      */
    305     public static void processAllUsers(List<UserHandleCompat> users, Context context) {
    306         if (!Utilities.ATLEAST_LOLLIPOP) {
    307             return;
    308         }
    309         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
    310         HashSet<String> validKeys = new HashSet<String>();
    311         for (UserHandleCompat user : users) {
    312             addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys);
    313         }
    314 
    315         SharedPreferences prefs = context.getSharedPreferences(
    316                 LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
    317                 Context.MODE_PRIVATE);
    318         SharedPreferences.Editor editor = prefs.edit();
    319         for (String key : prefs.getAll().keySet()) {
    320             if (!validKeys.contains(key)) {
    321                 editor.remove(key);
    322             }
    323         }
    324         editor.apply();
    325     }
    326 
    327     private static void addAllUserKeys(long userSerial, HashSet<String> keysOut) {
    328         keysOut.add(INSTALLED_PACKAGES_PREFIX + userSerial);
    329         keysOut.add(USER_FOLDER_ID_PREFIX + userSerial);
    330     }
    331 
    332     /**
    333      * For each user, if a work folder has not been created, mark it such that the folder will
    334      * never get created.
    335      */
    336     public static void markExistingUsersForNoFolderCreation(Context context) {
    337         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
    338         UserHandleCompat myUser = UserHandleCompat.myUserHandle();
    339 
    340         SharedPreferences prefs = null;
    341         for (UserHandleCompat user : userManager.getUserProfiles()) {
    342             if (myUser.equals(user)) {
    343                 continue;
    344             }
    345 
    346             if (prefs == null) {
    347                 prefs = context.getSharedPreferences(
    348                         LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
    349                         Context.MODE_PRIVATE);
    350             }
    351             String folderIdKey = USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user);
    352             if (!prefs.contains(folderIdKey)) {
    353                 prefs.edit().putLong(folderIdKey, ItemInfo.NO_ID).apply();
    354             }
    355         }
    356     }
    357 }
    358