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