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