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.content.Context;
20 import android.content.SharedPreferences;
21 import android.content.pm.LauncherActivityInfo;
22 import android.os.Process;
23 import android.os.UserHandle;
24 import android.support.v4.os.BuildCompat;
25 
26 import com.android.launcher3.AppInfo;
27 import com.android.launcher3.FolderInfo;
28 import com.android.launcher3.IconCache;
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.SessionCommitReceiver;
36 import com.android.launcher3.ShortcutInfo;
37 import com.android.launcher3.compat.UserManagerCompat;
38 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
39 
40 import java.util.ArrayList;
41 import java.util.HashSet;
42 import java.util.List;
43 
44 /**
45  * Handles addition of app shortcuts for managed profiles.
46  * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
47  */
48 public class ManagedProfileHeuristic {
49 
50     /**
51      * Maintain a set of packages installed per user.
52      */
53     private static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_";
54 
55     private static final String USER_FOLDER_ID_PREFIX = "user_folder_";
56 
57     /**
58      * Duration (in milliseconds) for which app shortcuts will be added to work folder.
59      */
60     private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
61 
get(Context context, UserHandle user)62     public static ManagedProfileHeuristic get(Context context, UserHandle user) {
63         if (!Process.myUserHandle().equals(user)) {
64             return new ManagedProfileHeuristic(context, user);
65         }
66         return null;
67     }
68 
69     private final Context mContext;
70     private final LauncherModel mModel;
71     private final UserHandle mUser;
72     private final IconCache mIconCache;
73     private final boolean mAddIconsToHomescreen;
74 
ManagedProfileHeuristic(Context context, UserHandle user)75     private ManagedProfileHeuristic(Context context, UserHandle user) {
76         mContext = context;
77         mUser = user;
78         mModel = LauncherAppState.getInstance(context).getModel();
79         mIconCache = LauncherAppState.getInstance(context).getIconCache();
80         mAddIconsToHomescreen =
81                 !BuildCompat.isAtLeastO() || SessionCommitReceiver.isEnabled(context);
82     }
83 
processPackageRemoved(String[] packages)84     public void processPackageRemoved(String[] packages) {
85         Preconditions.assertWorkerThread();
86         ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler();
87         for (String pkg : packages) {
88             handler.onPackageRemoved(pkg, mUser);
89         }
90     }
91 
processPackageAdd(String[] packages)92     public void processPackageAdd(String[] packages) {
93         Preconditions.assertWorkerThread();
94         ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler();
95         for (String pkg : packages) {
96             handler.onPackageAdded(pkg, mUser);
97         }
98     }
99 
processUserApps(List<LauncherActivityInfo> apps)100     public void processUserApps(List<LauncherActivityInfo> apps) {
101         Preconditions.assertWorkerThread();
102         new ManagedProfilePackageHandler().processUserApps(apps, mUser);
103     }
104 
105     private class ManagedProfilePackageHandler extends CachedPackageTracker {
106 
ManagedProfilePackageHandler()107         private ManagedProfilePackageHandler() {
108             super(mContext, LauncherFiles.MANAGED_USER_PREFERENCES_KEY);
109         }
110 
onLauncherAppsAdded( List<LauncherActivityInstallInfo> apps, UserHandle user, boolean userAppsExisted)111         protected void onLauncherAppsAdded(
112                 List<LauncherActivityInstallInfo> apps, UserHandle user, boolean userAppsExisted) {
113             ArrayList<ShortcutInfo> workFolderApps = new ArrayList<>();
114             ArrayList<ShortcutInfo> homescreenApps = new ArrayList<>();
115 
116             int count = apps.size();
117             long folderCreationTime =
118                     mUserManager.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION;
119 
120             boolean quietModeEnabled = UserManagerCompat.getInstance(mContext)
121                     .isQuietModeEnabled(user);
122             for (int i = 0; i < count; i++) {
123                 LauncherActivityInstallInfo info = apps.get(i);
124                 AppInfo appInfo = new AppInfo(info.info, user, quietModeEnabled);
125                 mIconCache.getTitleAndIcon(appInfo, info.info, false /* useLowResIcon */);
126                 ShortcutInfo si = appInfo.makeShortcut();
127                 ((info.installTime <= folderCreationTime) ? workFolderApps : homescreenApps).add(si);
128             }
129 
130             finalizeWorkFolder(user, workFolderApps, homescreenApps);
131 
132             // Do not add shortcuts on the homescreen for the first time. This prevents the launcher
133             // getting filled with the managed user apps, when it start with a fresh DB (or after
134             // a very long time).
135             if (userAppsExisted && !homescreenApps.isEmpty() && mAddIconsToHomescreen) {
136                 mModel.addAndBindAddedWorkspaceItems(new ArrayList<ItemInfo>(homescreenApps));
137             }
138         }
139 
140         @Override
onLauncherPackageRemoved(String packageName, UserHandle user)141         protected void onLauncherPackageRemoved(String packageName, UserHandle user) {
142         }
143 
144         /**
145          * Adds and binds shortcuts marked to be added to the work folder.
146          */
finalizeWorkFolder( UserHandle user, final ArrayList<ShortcutInfo> workFolderApps, ArrayList<ShortcutInfo> homescreenApps)147         private void finalizeWorkFolder(
148                 UserHandle user, final ArrayList<ShortcutInfo> workFolderApps,
149                 ArrayList<ShortcutInfo> homescreenApps) {
150             if (workFolderApps.isEmpty()) {
151                 return;
152             }
153             // Try to get a work folder.
154             String folderIdKey = USER_FOLDER_ID_PREFIX + mUserManager.getSerialNumberForUser(user);
155             if (!mAddIconsToHomescreen) {
156                 if (!mPrefs.contains(folderIdKey)) {
157                     // Just mark the folder id preference to avoid new folder creation later.
158                     mPrefs.edit().putLong(folderIdKey, -1).apply();
159                 }
160                 return;
161             }
162             if (mPrefs.contains(folderIdKey)) {
163                 long folderId = mPrefs.getLong(folderIdKey, 0);
164                 final FolderInfo workFolder = mModel.findFolderById(folderId);
165 
166                 if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) {
167                     // Could not get a work folder. Add all the icons to homescreen.
168                     homescreenApps.addAll(0, workFolderApps);
169                     return;
170                 }
171                 saveWorkFolderShortcuts(folderId, workFolder.contents.size(), workFolderApps);
172 
173                 // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
174                 new MainThreadExecutor().execute(new Runnable() {
175 
176                     @Override
177                     public void run() {
178                         workFolder.prepareAutoUpdate();
179                         for (ShortcutInfo info : workFolderApps) {
180                             workFolder.add(info, false);
181                         }
182                     }
183                 });
184             } else {
185                 // Create a new folder.
186                 final FolderInfo workFolder = new FolderInfo();
187                 workFolder.title = mContext.getText(R.string.work_folder_name);
188                 workFolder.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null);
189 
190                 // Add all shortcuts before adding it to the UI, as an empty folder might get deleted.
191                 for (ShortcutInfo info : workFolderApps) {
192                     workFolder.add(info, false);
193                 }
194 
195                 // Add the item to home screen and DB. This also generates an item id synchronously.
196                 ArrayList<ItemInfo> itemList = new ArrayList<>(1);
197                 itemList.add(workFolder);
198                 mModel.addAndBindAddedWorkspaceItems(itemList);
199                 mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
200 
201                 saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
202             }
203         }
204 
205         @Override
onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandle user)206         public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
207                 UserHandle user) {
208             // Do nothing
209         }
210     }
211 
212     /**
213      * Add work folder shortcuts to the DB.
214      */
saveWorkFolderShortcuts( long workFolderId, int startingRank, ArrayList<ShortcutInfo> workFolderApps)215     private void saveWorkFolderShortcuts(
216             long workFolderId, int startingRank, ArrayList<ShortcutInfo> workFolderApps) {
217         for (ItemInfo info : workFolderApps) {
218             info.rank = startingRank++;
219             mModel.getWriter(false).addItemToDatabase(info, workFolderId, 0, 0, 0);
220         }
221     }
222 
223     /**
224      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
225      */
processAllUsers(List<UserHandle> users, Context context)226     public static void processAllUsers(List<UserHandle> users, Context context) {
227         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
228         HashSet<String> validKeys = new HashSet<String>();
229         for (UserHandle user : users) {
230             addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys);
231         }
232 
233         SharedPreferences prefs = context.getSharedPreferences(
234                 LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
235                 Context.MODE_PRIVATE);
236         SharedPreferences.Editor editor = prefs.edit();
237         for (String key : prefs.getAll().keySet()) {
238             if (!validKeys.contains(key)) {
239                 editor.remove(key);
240             }
241         }
242         editor.apply();
243     }
244 
addAllUserKeys(long userSerial, HashSet<String> keysOut)245     private static void addAllUserKeys(long userSerial, HashSet<String> keysOut) {
246         keysOut.add(INSTALLED_PACKAGES_PREFIX + userSerial);
247         keysOut.add(USER_FOLDER_ID_PREFIX + userSerial);
248     }
249 
250     /**
251      * For each user, if a work folder has not been created, mark it such that the folder will
252      * never get created.
253      */
markExistingUsersForNoFolderCreation(Context context)254     public static void markExistingUsersForNoFolderCreation(Context context) {
255         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
256         UserHandle myUser = Process.myUserHandle();
257 
258         SharedPreferences prefs = null;
259         for (UserHandle user : userManager.getUserProfiles()) {
260             if (myUser.equals(user)) {
261                 continue;
262             }
263 
264             if (prefs == null) {
265                 prefs = context.getSharedPreferences(
266                         LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
267                         Context.MODE_PRIVATE);
268             }
269             String folderIdKey = USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user);
270             if (!prefs.contains(folderIdKey)) {
271                 prefs.edit().putLong(folderIdKey, ItemInfo.NO_ID).apply();
272             }
273         }
274     }
275 }
276