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