1 /*
2  * Copyright (C) 2019 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 package com.android.launcher3.pm;
17 
18 import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
19 import static com.android.launcher3.pm.PackageInstallInfo.STATUS_FAILED;
20 import static com.android.launcher3.pm.PackageInstallInfo.STATUS_INSTALLED;
21 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
22 
23 import android.content.pm.LauncherApps;
24 import android.content.pm.PackageInstaller;
25 import android.content.pm.PackageInstaller.SessionInfo;
26 import android.os.Build;
27 import android.os.UserHandle;
28 import android.util.SparseArray;
29 
30 import androidx.annotation.WorkerThread;
31 
32 import com.android.launcher3.util.PackageUserKey;
33 
34 import java.lang.ref.WeakReference;
35 
36 @WorkerThread
37 public class InstallSessionTracker extends PackageInstaller.SessionCallback {
38 
39     // Lazily initialized
40     private SparseArray<PackageUserKey> mActiveSessions = null;
41 
42     private final WeakReference<InstallSessionHelper> mWeakHelper;
43     private final WeakReference<Callback> mWeakCallback;
44     private final PackageInstaller mInstaller;
45     private final LauncherApps mLauncherApps;
46 
47 
InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback, PackageInstaller installer, LauncherApps launcherApps)48     InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback,
49             PackageInstaller installer, LauncherApps launcherApps) {
50         mWeakHelper = new WeakReference<>(installerCompat);
51         mWeakCallback = new WeakReference<>(callback);
52         mInstaller = installer;
53         mLauncherApps = launcherApps;
54     }
55 
56     @Override
onCreated(int sessionId)57     public void onCreated(int sessionId) {
58         InstallSessionHelper helper = mWeakHelper.get();
59         Callback callback = mWeakCallback.get();
60         if (callback == null || helper == null) {
61             return;
62         }
63         SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
64         if (sessionInfo != null) {
65             callback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
66         }
67 
68         helper.tryQueuePromiseAppIcon(sessionInfo);
69     }
70 
71     @Override
onFinished(int sessionId, boolean success)72     public void onFinished(int sessionId, boolean success) {
73         InstallSessionHelper helper = mWeakHelper.get();
74         Callback callback = mWeakCallback.get();
75         if (callback == null || helper == null) {
76             return;
77         }
78         // For a finished session, we can't get the session info. So use the
79         // packageName from our local cache.
80         SparseArray<PackageUserKey> activeSessions = getActiveSessionMap(helper);
81         PackageUserKey key = activeSessions.get(sessionId);
82         activeSessions.remove(sessionId);
83 
84         if (key != null && key.mPackageName != null) {
85             String packageName = key.mPackageName;
86             PackageInstallInfo info = PackageInstallInfo.fromState(
87                     success ? STATUS_INSTALLED : STATUS_FAILED,
88                     packageName, key.mUser);
89             callback.onPackageStateChanged(info);
90 
91             if (!success && helper.promiseIconAddedForId(sessionId)) {
92                 callback.onSessionFailure(packageName, key.mUser);
93                 // If it is successful, the id is removed in the the package added flow.
94                 helper.removePromiseIconId(sessionId);
95             }
96         }
97     }
98 
99     @Override
onProgressChanged(int sessionId, float progress)100     public void onProgressChanged(int sessionId, float progress) {
101         InstallSessionHelper helper = mWeakHelper.get();
102         Callback callback = mWeakCallback.get();
103         if (callback == null || helper == null) {
104             return;
105         }
106         SessionInfo session = helper.getVerifiedSessionInfo(sessionId);
107         if (session != null && session.getAppPackageName() != null) {
108             callback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(session));
109         }
110     }
111 
112     @Override
onActiveChanged(int sessionId, boolean active)113     public void onActiveChanged(int sessionId, boolean active) { }
114 
115     @Override
onBadgingChanged(int sessionId)116     public void onBadgingChanged(int sessionId) {
117         InstallSessionHelper helper = mWeakHelper.get();
118         Callback callback = mWeakCallback.get();
119         if (callback == null || helper == null) {
120             return;
121         }
122         SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
123         if (sessionInfo != null) {
124             helper.tryQueuePromiseAppIcon(sessionInfo);
125         }
126     }
127 
pushSessionDisplayToLauncher( int sessionId, InstallSessionHelper helper, Callback callback)128     private SessionInfo pushSessionDisplayToLauncher(
129             int sessionId, InstallSessionHelper helper, Callback callback) {
130         SessionInfo session = helper.getVerifiedSessionInfo(sessionId);
131         if (session != null && session.getAppPackageName() != null) {
132             PackageUserKey key =
133                     new PackageUserKey(session.getAppPackageName(), getUserHandle(session));
134             getActiveSessionMap(helper).put(session.getSessionId(), key);
135             callback.onUpdateSessionDisplay(key, session);
136             return session;
137         }
138         return null;
139     }
140 
getActiveSessionMap(InstallSessionHelper helper)141     private SparseArray<PackageUserKey> getActiveSessionMap(InstallSessionHelper helper) {
142         if (mActiveSessions == null) {
143             mActiveSessions = new SparseArray<>();
144             helper.getActiveSessions().forEach(
145                     (key, si) -> mActiveSessions.put(si.getSessionId(), key));
146         }
147         return mActiveSessions;
148     }
149 
register()150     void register() {
151         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
152             mInstaller.registerSessionCallback(this, MODEL_EXECUTOR.getHandler());
153         } else {
154             mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, this);
155         }
156     }
157 
unregister()158     public void unregister() {
159         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
160             mInstaller.unregisterSessionCallback(this);
161         } else {
162             mLauncherApps.unregisterPackageInstallerSessionCallback(this);
163         }
164     }
165 
166     public interface Callback {
167 
onSessionFailure(String packageName, UserHandle user)168         void onSessionFailure(String packageName, UserHandle user);
169 
onUpdateSessionDisplay(PackageUserKey key, SessionInfo info)170         void onUpdateSessionDisplay(PackageUserKey key, SessionInfo info);
171 
onPackageStateChanged(PackageInstallInfo info)172         void onPackageStateChanged(PackageInstallInfo info);
173 
onInstallSessionCreated(PackageInstallInfo info)174         void onInstallSessionCreated(PackageInstallInfo info);
175     }
176 }
177