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.Process; 26 import android.os.UserHandle; 27 import android.text.TextUtils; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 import androidx.annotation.WorkerThread; 32 33 import com.android.launcher3.LauncherPrefs; 34 import com.android.launcher3.SessionCommitReceiver; 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.logging.FileLog; 37 import com.android.launcher3.model.ItemInstallQueue; 38 import com.android.launcher3.util.IntArray; 39 import com.android.launcher3.util.IntSet; 40 import com.android.launcher3.util.MainThreadInitializedObject; 41 import com.android.launcher3.util.PackageManagerHelper; 42 import com.android.launcher3.util.PackageUserKey; 43 import com.android.launcher3.util.Preconditions; 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 public class InstallSessionHelper { 55 56 @NonNull 57 private static final String LOG = "InstallSessionHelper"; 58 59 // Set<String> of session ids of promise icons that have been added to the home screen 60 // as FLAG_PROMISE_NEW_INSTALLS. 61 @NonNull 62 public static final String PROMISE_ICON_IDS = "promise_icon_ids"; 63 64 private static final boolean DEBUG = false; 65 66 @NonNull 67 public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE = 68 new MainThreadInitializedObject<>(InstallSessionHelper::new); 69 70 @Nullable 71 private final LauncherApps mLauncherApps; 72 73 @NonNull 74 private final Context mAppContext; 75 76 @NonNull 77 private final PackageInstaller mInstaller; 78 79 @NonNull 80 private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>(); 81 82 @Nullable 83 private IntSet mPromiseIconIds; 84 InstallSessionHelper(@onNull final Context context)85 public InstallSessionHelper(@NonNull final Context context) { 86 mInstaller = context.getPackageManager().getPackageInstaller(); 87 mAppContext = context.getApplicationContext(); 88 mLauncherApps = context.getSystemService(LauncherApps.class); 89 } 90 91 @WorkerThread 92 @NonNull getPromiseIconIds()93 private IntSet getPromiseIconIds() { 94 Preconditions.assertWorkerThread(); 95 if (mPromiseIconIds != null) { 96 return mPromiseIconIds; 97 } 98 mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString( 99 LauncherPrefs.get(mAppContext).get(LauncherPrefs.PROMISE_ICON_IDS))); 100 101 IntArray existingIds = new IntArray(); 102 for (SessionInfo info : getActiveSessions().values()) { 103 existingIds.add(info.getSessionId()); 104 } 105 IntArray idsToRemove = new IntArray(); 106 107 for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) { 108 if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) { 109 idsToRemove.add(mPromiseIconIds.getArray().get(i)); 110 } 111 } 112 for (int i = idsToRemove.size() - 1; i >= 0; --i) { 113 mPromiseIconIds.getArray().removeValue(idsToRemove.get(i)); 114 } 115 return mPromiseIconIds; 116 } 117 118 @NonNull getActiveSessions()119 public HashMap<PackageUserKey, SessionInfo> getActiveSessions() { 120 HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>(); 121 for (SessionInfo info : getAllVerifiedSessions()) { 122 activePackages.put(new PackageUserKey(info.getAppPackageName(), getUserHandle(info)), 123 info); 124 } 125 return activePackages; 126 } 127 128 @Nullable getActiveSessionInfo(UserHandle user, String pkg)129 public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) { 130 for (SessionInfo info : getAllVerifiedSessions()) { 131 boolean match = pkg.equals(info.getAppPackageName()); 132 if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) { 133 match = false; 134 } 135 if (match) { 136 return info; 137 } 138 } 139 return null; 140 } 141 updatePromiseIconPrefs()142 private void updatePromiseIconPrefs() { 143 LauncherPrefs.get(mAppContext).put(LauncherPrefs.PROMISE_ICON_IDS, 144 getPromiseIconIds().getArray().toConcatString()); 145 } 146 147 @Nullable getVerifiedSessionInfo(final int sessionId)148 SessionInfo getVerifiedSessionInfo(final int sessionId) { 149 return verify(mInstaller.getSessionInfo(sessionId)); 150 } 151 152 @Nullable verify(@ullable final SessionInfo sessionInfo)153 private SessionInfo verify(@Nullable final SessionInfo sessionInfo) { 154 if (sessionInfo == null 155 || sessionInfo.getInstallerPackageName() == null 156 || TextUtils.isEmpty(sessionInfo.getAppPackageName())) { 157 return null; 158 } 159 return isTrustedPackage(sessionInfo.getInstallerPackageName(), getUserHandle(sessionInfo)) 160 ? sessionInfo : null; 161 } 162 163 /** 164 * Returns true if the provided packageName can be trusted for user configurations 165 */ isTrustedPackage(String pkg, UserHandle user)166 public boolean isTrustedPackage(String pkg, UserHandle user) { 167 synchronized (mSessionVerifiedMap) { 168 if (!mSessionVerifiedMap.containsKey(pkg)) { 169 boolean hasSystemFlag = DEBUG || mAppContext.getPackageName().equals(pkg) 170 || new PackageManagerHelper(mAppContext) 171 .getApplicationInfo(pkg, user, ApplicationInfo.FLAG_SYSTEM) != null; 172 mSessionVerifiedMap.put(pkg, hasSystemFlag); 173 } 174 } 175 return mSessionVerifiedMap.get(pkg); 176 } 177 178 @NonNull getAllVerifiedSessions()179 public List<SessionInfo> getAllVerifiedSessions() { 180 List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q 181 ? Objects.requireNonNull(mLauncherApps).getAllPackageInstallerSessions() 182 : mInstaller.getAllSessions()); 183 Iterator<SessionInfo> it = list.iterator(); 184 while (it.hasNext()) { 185 if (verify(it.next()) == null) { 186 it.remove(); 187 } 188 } 189 return list; 190 } 191 192 @WorkerThread promiseIconAddedForId(final int sessionId)193 public boolean promiseIconAddedForId(final int sessionId) { 194 return getPromiseIconIds().contains(sessionId); 195 } 196 197 @WorkerThread removePromiseIconId(final int sessionId)198 public void removePromiseIconId(final int sessionId) { 199 if (promiseIconAddedForId(sessionId)) { 200 getPromiseIconIds().getArray().removeValue(sessionId); 201 updatePromiseIconPrefs(); 202 } 203 } 204 205 /** 206 * Add a promise app icon to the workspace iff: 207 * - The settings for it are enabled 208 * - The user installed the app 209 * - There is an app icon and label (For apps with no launching activity, no icon is provided). 210 * - The app is not already installed 211 * - A promise icon for the session has not already been created 212 */ 213 @WorkerThread tryQueuePromiseAppIcon(@ullable final PackageInstaller.SessionInfo sessionInfo)214 void tryQueuePromiseAppIcon(@Nullable final PackageInstaller.SessionInfo sessionInfo) { 215 if (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(@ullable final PackageInstaller.SessionInfo sessionInfo)229 public boolean verifySessionInfo(@Nullable final 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( @ullable final InstallSessionTracker.Callback callback)238 public InstallSessionTracker registerInstallTracker( 239 @Nullable final InstallSessionTracker.Callback callback) { 240 InstallSessionTracker tracker = new InstallSessionTracker( 241 this, callback, mInstaller, mLauncherApps); 242 tracker.register(); 243 return tracker; 244 } 245 getUserHandle(@onNull final SessionInfo info)246 public static UserHandle getUserHandle(@NonNull final SessionInfo info) { 247 return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle(); 248 } 249 } 250