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