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