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