/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.util; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.LauncherActivityInfo; import android.os.Handler; import android.os.Process; import android.os.UserHandle; import com.android.launcher3.FolderInfo; import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherFiles; import com.android.launcher3.LauncherModel; import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.ModelWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.List; /** * Handles addition of app shortcuts for managed profiles. * Methods of class should only be called on {@link LauncherModel#sWorkerThread}. */ public class ManagedProfileHeuristic { private static final String USER_FOLDER_ID_PREFIX = "user_folder_"; /** * Duration (in milliseconds) for which app shortcuts will be added to work folder. */ private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000; public static void onAllAppsLoaded(final Context context, List apps, UserHandle user) { if (Process.myUserHandle().equals(user)) { return; } UserFolderInfo ufi = new UserFolderInfo(context, user, null); // We only handle folder creation once. Later icon additions are handled using package // or session events. if (ufi.folderAlreadyCreated) { return; } if (Utilities.ATLEAST_OREO && !SessionCommitReceiver.isEnabled(context)) { // Just mark the folder id preference to avoid new folder creation later. ufi.prefs.edit().putLong(ufi.folderIdKey, ItemInfo.NO_ID).apply(); return; } InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_BULK_ADD); for (LauncherActivityInfo app : apps) { // Queue all items which should go in the work folder. if (app.getFirstInstallTime() < ufi.addIconToFolderTime) { InstallShortcutReceiver.queueActivityInfo(app, context); } } // Post the queue update on next frame, so that the loader gets finished. new Handler(LauncherModel.getWorkerLooper()).post(new Runnable() { @Override public void run() { InstallShortcutReceiver.disableAndFlushInstallQueue( InstallShortcutReceiver.FLAG_BULK_ADD, context); } }); } /** * Utility class to help workspace icon addition. */ public static class UserFolderInfo { final ArrayList pendingShortcuts = new ArrayList<>(); final UserHandle user; final long userSerial; // Time until which icons will be added to folder instead. final long addIconToFolderTime; final String folderIdKey; final SharedPreferences prefs; final boolean folderAlreadyCreated; final FolderInfo folderInfo; boolean folderPendingAddition; public UserFolderInfo(Context context, UserHandle user, BgDataModel dataModel) { this.user = user; UserManagerCompat um = UserManagerCompat.getInstance(context); userSerial = um.getSerialNumberForUser(user); addIconToFolderTime = um.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION; folderIdKey = USER_FOLDER_ID_PREFIX + userSerial; prefs = prefs(context); folderAlreadyCreated = prefs.contains(folderIdKey); if (dataModel != null) { if (folderAlreadyCreated) { long folderId = prefs.getLong(folderIdKey, ItemInfo.NO_ID); folderInfo = dataModel.folders.get(folderId); } else { folderInfo = new FolderInfo(); folderInfo.title = context.getText(R.string.work_folder_name); folderInfo.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null); folderPendingAddition = true; } } else { folderInfo = null; } } /** * Returns the ItemInfo which should be added to the workspace. In case the the provided * {@link ShortcutInfo} or a wrapped {@link FolderInfo} or null. */ public ItemInfo convertToWorkspaceItem( ShortcutInfo shortcut, LauncherActivityInfo activityInfo) { if (activityInfo.getFirstInstallTime() >= addIconToFolderTime) { return shortcut; } if (folderAlreadyCreated) { if (folderInfo == null) { // Work folder was deleted by user, add icon to home screen. return shortcut; } else { // Add item to work folder instead. Nothing needs to be added // on the homescreen. pendingShortcuts.add(shortcut); return null; } } pendingShortcuts.add(shortcut); folderInfo.add(shortcut, false); if (folderPendingAddition) { folderPendingAddition = false; return folderInfo; } else { // WorkFolder already requested to be added. Nothing new needs to be added. return null; } } public void applyPendingState(ModelWriter writer) { if (folderInfo == null) { return; } int startingRank = 0; if (folderAlreadyCreated) { startingRank = folderInfo.contents.size(); } for (ShortcutInfo info : pendingShortcuts) { info.rank = startingRank++; writer.addItemToDatabase(info, folderInfo.id, 0, 0, 0); } if (folderAlreadyCreated) { // FolderInfo could already be bound. We need to add shortcuts on the UI thread. new MainThreadExecutor().execute(new Runnable() { @Override public void run() { folderInfo.prepareAutoUpdate(); for (ShortcutInfo info : pendingShortcuts) { folderInfo.add(info, false); } } }); } else { prefs.edit().putLong(folderIdKey, folderInfo.id).apply(); } } } /** * Verifies that entries corresponding to {@param users} exist and removes all invalid entries. */ public static void processAllUsers(List users, Context context) { UserManagerCompat userManager = UserManagerCompat.getInstance(context); HashSet validKeys = new HashSet<>(); for (UserHandle user : users) { validKeys.add(USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user)); } SharedPreferences prefs = prefs(context); SharedPreferences.Editor editor = prefs.edit(); for (String key : prefs.getAll().keySet()) { if (!validKeys.contains(key)) { editor.remove(key); } } editor.apply(); } /** * For each user, if a work folder has not been created, mark it such that the folder will * never get created. */ public static void markExistingUsersForNoFolderCreation(Context context) { UserManagerCompat userManager = UserManagerCompat.getInstance(context); UserHandle myUser = Process.myUserHandle(); SharedPreferences prefs = null; for (UserHandle user : userManager.getUserProfiles()) { if (myUser.equals(user)) { continue; } if (prefs == null) { prefs = prefs(context); } String folderIdKey = USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user); if (!prefs.contains(folderIdKey)) { prefs.edit().putLong(folderIdKey, ItemInfo.NO_ID).apply(); } } } public static SharedPreferences prefs(Context context) { return context.getSharedPreferences( LauncherFiles.MANAGED_USER_PREFERENCES_KEY, Context.MODE_PRIVATE); } }