1 /* 2 * Copyright (C) 2014 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.pm; 18 19 import static com.android.launcher3.Utilities.getPrefs; 20 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.LauncherApps; 24 import android.content.pm.PackageInstaller; 25 import android.content.pm.PackageInstaller.SessionInfo; 26 import android.content.pm.PackageManager; 27 import android.os.Process; 28 import android.os.UserHandle; 29 import android.text.TextUtils; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.RequiresApi; 33 import androidx.annotation.WorkerThread; 34 35 import com.android.launcher3.LauncherSettings; 36 import com.android.launcher3.SessionCommitReceiver; 37 import com.android.launcher3.Utilities; 38 import com.android.launcher3.config.FeatureFlags; 39 import com.android.launcher3.logging.FileLog; 40 import com.android.launcher3.model.ItemInstallQueue; 41 import com.android.launcher3.util.IntArray; 42 import com.android.launcher3.util.IntSet; 43 import com.android.launcher3.util.MainThreadInitializedObject; 44 import com.android.launcher3.util.PackageManagerHelper; 45 import com.android.launcher3.util.PackageUserKey; 46 import com.android.launcher3.util.Preconditions; 47 48 import java.util.ArrayList; 49 import java.util.HashMap; 50 import java.util.Iterator; 51 import java.util.List; 52 53 /** 54 * Utility class to tracking install sessions 55 */ 56 public class InstallSessionHelper { 57 58 private static final String LOG = "InstallSessionHelper"; 59 60 // Set<String> of session ids of promise icons that have been added to the home screen 61 // as FLAG_PROMISE_NEW_INSTALLS. 62 protected static final String PROMISE_ICON_IDS = "promise_icon_ids"; 63 64 private static final boolean DEBUG = false; 65 66 public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE = 67 new MainThreadInitializedObject<>(InstallSessionHelper::new); 68 69 private final LauncherApps mLauncherApps; 70 private final Context mAppContext; 71 72 private final PackageInstaller mInstaller; 73 private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>(); 74 75 private IntSet mPromiseIconIds; 76 InstallSessionHelper(Context context)77 public InstallSessionHelper(Context context) { 78 mInstaller = context.getPackageManager().getPackageInstaller(); 79 mAppContext = context.getApplicationContext(); 80 mLauncherApps = context.getSystemService(LauncherApps.class); 81 } 82 83 @WorkerThread getPromiseIconIds()84 private IntSet getPromiseIconIds() { 85 Preconditions.assertWorkerThread(); 86 if (mPromiseIconIds != null) { 87 return mPromiseIconIds; 88 } 89 mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString( 90 getPrefs(mAppContext).getString(PROMISE_ICON_IDS, ""))); 91 92 IntArray existingIds = new IntArray(); 93 for (SessionInfo info : getActiveSessions().values()) { 94 existingIds.add(info.getSessionId()); 95 } 96 IntArray idsToRemove = new IntArray(); 97 98 for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) { 99 if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) { 100 idsToRemove.add(mPromiseIconIds.getArray().get(i)); 101 } 102 } 103 for (int i = idsToRemove.size() - 1; i >= 0; --i) { 104 mPromiseIconIds.getArray().removeValue(idsToRemove.get(i)); 105 } 106 return mPromiseIconIds; 107 } 108 getActiveSessions()109 public HashMap<PackageUserKey, SessionInfo> getActiveSessions() { 110 HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>(); 111 for (SessionInfo info : getAllVerifiedSessions()) { 112 activePackages.put(new PackageUserKey(info.getAppPackageName(), getUserHandle(info)), 113 info); 114 } 115 return activePackages; 116 } 117 getActiveSessionInfo(UserHandle user, String pkg)118 public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) { 119 for (SessionInfo info : getAllVerifiedSessions()) { 120 boolean match = pkg.equals(info.getAppPackageName()); 121 if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) { 122 match = false; 123 } 124 if (match) { 125 return info; 126 } 127 } 128 return null; 129 } 130 updatePromiseIconPrefs()131 private void updatePromiseIconPrefs() { 132 getPrefs(mAppContext).edit() 133 .putString(PROMISE_ICON_IDS, getPromiseIconIds().getArray().toConcatString()) 134 .apply(); 135 } 136 getVerifiedSessionInfo(int sessionId)137 SessionInfo getVerifiedSessionInfo(int sessionId) { 138 return verify(mInstaller.getSessionInfo(sessionId)); 139 } 140 verify(SessionInfo sessionInfo)141 private SessionInfo verify(SessionInfo sessionInfo) { 142 if (sessionInfo == null 143 || sessionInfo.getInstallerPackageName() == null 144 || TextUtils.isEmpty(sessionInfo.getAppPackageName())) { 145 return null; 146 } 147 String pkg = sessionInfo.getInstallerPackageName(); 148 synchronized (mSessionVerifiedMap) { 149 if (!mSessionVerifiedMap.containsKey(pkg)) { 150 boolean hasSystemFlag = new PackageManagerHelper(mAppContext).getApplicationInfo( 151 pkg, getUserHandle(sessionInfo), ApplicationInfo.FLAG_SYSTEM) != null; 152 mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag); 153 } 154 } 155 return mSessionVerifiedMap.get(pkg) ? sessionInfo : null; 156 } 157 getAllVerifiedSessions()158 public List<SessionInfo> getAllVerifiedSessions() { 159 List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q 160 ? mLauncherApps.getAllPackageInstallerSessions() 161 : mInstaller.getAllSessions()); 162 Iterator<SessionInfo> it = list.iterator(); 163 while (it.hasNext()) { 164 if (verify(it.next()) == null) { 165 it.remove(); 166 } 167 } 168 return list; 169 } 170 171 /** 172 * Attempt to restore workspace layout if the session is triggered due to device restore. 173 */ restoreDbIfApplicable(@onNull final SessionInfo info)174 public boolean restoreDbIfApplicable(@NonNull final SessionInfo info) { 175 if (!FeatureFlags.ENABLE_DATABASE_RESTORE.get()) { 176 return false; 177 } 178 if (isRestore(info)) { 179 LauncherSettings.Settings.call(mAppContext.getContentResolver(), 180 LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE); 181 return true; 182 } 183 return false; 184 } 185 186 @RequiresApi(26) isRestore(@onNull final SessionInfo info)187 private static boolean isRestore(@NonNull final SessionInfo info) { 188 return info.getInstallReason() == PackageManager.INSTALL_REASON_DEVICE_RESTORE; 189 } 190 191 @WorkerThread promiseIconAddedForId(int sessionId)192 public boolean promiseIconAddedForId(int sessionId) { 193 return getPromiseIconIds().contains(sessionId); 194 } 195 196 @WorkerThread removePromiseIconId(int sessionId)197 public void removePromiseIconId(int sessionId) { 198 if (promiseIconAddedForId(sessionId)) { 199 getPromiseIconIds().getArray().removeValue(sessionId); 200 updatePromiseIconPrefs(); 201 } 202 } 203 204 /** 205 * Add a promise app icon to the workspace iff: 206 * - The settings for it are enabled 207 * - The user installed the app 208 * - There is an app icon and label (For apps with no launching activity, no icon is provided). 209 * - The app is not already installed 210 * - A promise icon for the session has not already been created 211 */ 212 @WorkerThread tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo)213 void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) { 214 if (FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get() 215 && SessionCommitReceiver.isEnabled(mAppContext) 216 && verifySessionInfo(sessionInfo) 217 && !promiseIconAddedForId(sessionInfo.getSessionId())) { 218 FileLog.d(LOG, "Adding package name to install queue: " 219 + sessionInfo.getAppPackageName()); 220 221 ItemInstallQueue.INSTANCE.get(mAppContext) 222 .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo)); 223 224 getPromiseIconIds().add(sessionInfo.getSessionId()); 225 updatePromiseIconPrefs(); 226 } 227 } 228 verifySessionInfo(PackageInstaller.SessionInfo sessionInfo)229 public boolean verifySessionInfo(PackageInstaller.SessionInfo sessionInfo) { 230 return verify(sessionInfo) != null 231 && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER 232 && sessionInfo.getAppIcon() != null 233 && !TextUtils.isEmpty(sessionInfo.getAppLabel()) 234 && !new PackageManagerHelper(mAppContext).isAppInstalled( 235 sessionInfo.getAppPackageName(), getUserHandle(sessionInfo)); 236 } 237 registerInstallTracker(InstallSessionTracker.Callback callback)238 public InstallSessionTracker registerInstallTracker(InstallSessionTracker.Callback callback) { 239 InstallSessionTracker tracker = new InstallSessionTracker( 240 this, callback, mInstaller, mLauncherApps); 241 tracker.register(); 242 return tracker; 243 } 244 getUserHandle(SessionInfo info)245 public static UserHandle getUserHandle(SessionInfo info) { 246 return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle(); 247 } 248 } 249