• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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