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