2019-09-20 12:51:37 -07:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
|
|
|
*
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package com.android.launcher3.pm;
|
|
|
|
|
|
|
|
|
|
import static com.android.launcher3.Utilities.getPrefs;
|
2020-10-23 09:26:44 -07:00
|
|
|
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
2019-09-20 12:51:37 -07:00
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.content.pm.ApplicationInfo;
|
|
|
|
|
import android.content.pm.LauncherApps;
|
|
|
|
|
import android.content.pm.PackageInstaller;
|
|
|
|
|
import android.content.pm.PackageInstaller.SessionInfo;
|
|
|
|
|
import android.content.pm.PackageManager;
|
2021-04-19 17:02:23 -07:00
|
|
|
import android.graphics.Bitmap;
|
2019-09-20 12:51:37 -07:00
|
|
|
import android.os.Build;
|
|
|
|
|
import android.os.Process;
|
|
|
|
|
import android.os.UserHandle;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
|
2019-12-10 14:56:34 -08:00
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
|
import androidx.annotation.RequiresApi;
|
2020-10-23 09:26:44 -07:00
|
|
|
import androidx.annotation.WorkerThread;
|
2019-12-10 14:56:34 -08:00
|
|
|
|
|
|
|
|
import com.android.launcher3.LauncherSettings;
|
2019-09-20 12:51:37 -07:00
|
|
|
import com.android.launcher3.SessionCommitReceiver;
|
|
|
|
|
import com.android.launcher3.Utilities;
|
|
|
|
|
import com.android.launcher3.config.FeatureFlags;
|
2021-05-04 11:40:05 -07:00
|
|
|
import com.android.launcher3.logging.FileLog;
|
2020-08-12 13:59:27 -07:00
|
|
|
import com.android.launcher3.model.ItemInstallQueue;
|
2021-04-19 17:02:23 -07:00
|
|
|
import com.android.launcher3.util.IOUtils;
|
2019-09-20 12:51:37 -07:00
|
|
|
import com.android.launcher3.util.IntArray;
|
|
|
|
|
import com.android.launcher3.util.IntSet;
|
2019-12-09 14:55:56 -08:00
|
|
|
import com.android.launcher3.util.MainThreadInitializedObject;
|
2019-10-02 16:13:34 -07:00
|
|
|
import com.android.launcher3.util.PackageManagerHelper;
|
2019-09-20 12:51:37 -07:00
|
|
|
import com.android.launcher3.util.PackageUserKey;
|
2020-10-23 09:26:44 -07:00
|
|
|
import com.android.launcher3.util.Preconditions;
|
2019-09-20 12:51:37 -07:00
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
2019-12-09 14:55:56 -08:00
|
|
|
/**
|
|
|
|
|
* Utility class to tracking install sessions
|
|
|
|
|
*/
|
|
|
|
|
public class InstallSessionHelper {
|
2019-09-20 12:51:37 -07:00
|
|
|
|
2021-03-25 13:51:20 -07:00
|
|
|
private static final String LOG = "InstallSessionHelper";
|
|
|
|
|
|
2019-09-20 12:51:37 -07:00
|
|
|
// Set<String> of session ids of promise icons that have been added to the home screen
|
|
|
|
|
// as FLAG_PROMISE_NEW_INSTALLS.
|
|
|
|
|
protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
|
|
|
|
|
|
|
|
|
|
private static final boolean DEBUG = false;
|
2019-12-09 14:55:56 -08:00
|
|
|
|
|
|
|
|
public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE =
|
|
|
|
|
new MainThreadInitializedObject<>(InstallSessionHelper::new);
|
|
|
|
|
|
2019-09-20 12:51:37 -07:00
|
|
|
private final LauncherApps mLauncherApps;
|
|
|
|
|
private final Context mAppContext;
|
|
|
|
|
|
|
|
|
|
private final PackageInstaller mInstaller;
|
|
|
|
|
private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>();
|
|
|
|
|
|
2020-10-23 09:26:44 -07:00
|
|
|
private IntSet mPromiseIconIds;
|
|
|
|
|
|
2019-12-09 14:55:56 -08:00
|
|
|
public InstallSessionHelper(Context context) {
|
2019-09-20 12:51:37 -07:00
|
|
|
mInstaller = context.getPackageManager().getPackageInstaller();
|
|
|
|
|
mAppContext = context.getApplicationContext();
|
|
|
|
|
mLauncherApps = context.getSystemService(LauncherApps.class);
|
2020-10-05 14:46:26 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-23 09:26:44 -07:00
|
|
|
@WorkerThread
|
|
|
|
|
private IntSet getPromiseIconIds() {
|
|
|
|
|
Preconditions.assertWorkerThread();
|
|
|
|
|
if (mPromiseIconIds != null) {
|
|
|
|
|
return mPromiseIconIds;
|
|
|
|
|
}
|
|
|
|
|
mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
|
|
|
|
|
getPrefs(mAppContext).getString(PROMISE_ICON_IDS, "")));
|
2020-10-05 14:46:26 +00:00
|
|
|
|
2019-09-20 12:51:37 -07:00
|
|
|
IntArray existingIds = new IntArray();
|
|
|
|
|
for (SessionInfo info : getActiveSessions().values()) {
|
|
|
|
|
existingIds.add(info.getSessionId());
|
|
|
|
|
}
|
|
|
|
|
IntArray idsToRemove = new IntArray();
|
|
|
|
|
|
|
|
|
|
for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) {
|
|
|
|
|
if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) {
|
|
|
|
|
idsToRemove.add(mPromiseIconIds.getArray().get(i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (int i = idsToRemove.size() - 1; i >= 0; --i) {
|
|
|
|
|
mPromiseIconIds.getArray().removeValue(idsToRemove.get(i));
|
|
|
|
|
}
|
2020-10-23 09:26:44 -07:00
|
|
|
return mPromiseIconIds;
|
2019-09-20 12:51:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public HashMap<PackageUserKey, SessionInfo> getActiveSessions() {
|
|
|
|
|
HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>();
|
|
|
|
|
for (SessionInfo info : getAllVerifiedSessions()) {
|
2019-10-02 16:13:34 -07:00
|
|
|
activePackages.put(new PackageUserKey(info.getAppPackageName(), getUserHandle(info)),
|
|
|
|
|
info);
|
2019-09-20 12:51:37 -07:00
|
|
|
}
|
|
|
|
|
return activePackages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) {
|
|
|
|
|
for (SessionInfo info : getAllVerifiedSessions()) {
|
|
|
|
|
boolean match = pkg.equals(info.getAppPackageName());
|
|
|
|
|
if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) {
|
|
|
|
|
match = false;
|
|
|
|
|
}
|
|
|
|
|
if (match) {
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void updatePromiseIconPrefs() {
|
|
|
|
|
getPrefs(mAppContext).edit()
|
2020-10-23 09:26:44 -07:00
|
|
|
.putString(PROMISE_ICON_IDS, getPromiseIconIds().getArray().toConcatString())
|
2019-09-20 12:51:37 -07:00
|
|
|
.apply();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SessionInfo getVerifiedSessionInfo(int sessionId) {
|
|
|
|
|
return verify(mInstaller.getSessionInfo(sessionId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private SessionInfo verify(SessionInfo sessionInfo) {
|
|
|
|
|
if (sessionInfo == null
|
|
|
|
|
|| sessionInfo.getInstallerPackageName() == null
|
|
|
|
|
|| TextUtils.isEmpty(sessionInfo.getAppPackageName())) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
String pkg = sessionInfo.getInstallerPackageName();
|
|
|
|
|
synchronized (mSessionVerifiedMap) {
|
|
|
|
|
if (!mSessionVerifiedMap.containsKey(pkg)) {
|
2019-10-02 16:13:34 -07:00
|
|
|
boolean hasSystemFlag = new PackageManagerHelper(mAppContext).getApplicationInfo(
|
|
|
|
|
pkg, getUserHandle(sessionInfo), ApplicationInfo.FLAG_SYSTEM) != null;
|
2019-09-20 12:51:37 -07:00
|
|
|
mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return mSessionVerifiedMap.get(pkg) ? sessionInfo : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<SessionInfo> getAllVerifiedSessions() {
|
|
|
|
|
List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q
|
|
|
|
|
? mLauncherApps.getAllPackageInstallerSessions()
|
|
|
|
|
: mInstaller.getAllSessions());
|
|
|
|
|
Iterator<SessionInfo> it = list.iterator();
|
|
|
|
|
while (it.hasNext()) {
|
|
|
|
|
if (verify(it.next()) == null) {
|
|
|
|
|
it.remove();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-10 14:56:34 -08:00
|
|
|
/**
|
2020-04-28 13:58:45 -07:00
|
|
|
* Attempt to restore workspace layout if the session is triggered due to device restore.
|
2019-12-10 14:56:34 -08:00
|
|
|
*/
|
|
|
|
|
public boolean restoreDbIfApplicable(@NonNull final SessionInfo info) {
|
2020-07-29 16:54:20 -07:00
|
|
|
if (!FeatureFlags.ENABLE_DATABASE_RESTORE.get()) {
|
2019-12-10 14:56:34 -08:00
|
|
|
return false;
|
|
|
|
|
}
|
2020-04-28 13:58:45 -07:00
|
|
|
if (isRestore(info)) {
|
2019-12-10 14:56:34 -08:00
|
|
|
LauncherSettings.Settings.call(mAppContext.getContentResolver(),
|
|
|
|
|
LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@RequiresApi(26)
|
|
|
|
|
private static boolean isRestore(@NonNull final SessionInfo info) {
|
|
|
|
|
return info.getInstallReason() == PackageManager.INSTALL_REASON_DEVICE_RESTORE;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 09:26:44 -07:00
|
|
|
@WorkerThread
|
2019-09-20 12:51:37 -07:00
|
|
|
public boolean promiseIconAddedForId(int sessionId) {
|
2020-10-23 09:26:44 -07:00
|
|
|
return getPromiseIconIds().contains(sessionId);
|
2019-09-20 12:51:37 -07:00
|
|
|
}
|
|
|
|
|
|
2020-10-23 09:26:44 -07:00
|
|
|
@WorkerThread
|
2019-09-20 12:51:37 -07:00
|
|
|
public void removePromiseIconId(int sessionId) {
|
2020-10-23 09:26:44 -07:00
|
|
|
if (promiseIconAddedForId(sessionId)) {
|
|
|
|
|
getPromiseIconIds().getArray().removeValue(sessionId);
|
2019-09-20 12:51:37 -07:00
|
|
|
updatePromiseIconPrefs();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a promise app icon to the workspace iff:
|
|
|
|
|
* - The settings for it are enabled
|
|
|
|
|
* - The user installed the app
|
|
|
|
|
* - There is an app icon and label (For apps with no launching activity, no icon is provided).
|
|
|
|
|
* - The app is not already installed
|
|
|
|
|
* - A promise icon for the session has not already been created
|
|
|
|
|
*/
|
2020-10-23 09:26:44 -07:00
|
|
|
@WorkerThread
|
2019-09-20 12:51:37 -07:00
|
|
|
void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
|
2020-07-29 16:54:20 -07:00
|
|
|
if (FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
|
2019-09-20 12:51:37 -07:00
|
|
|
&& SessionCommitReceiver.isEnabled(mAppContext)
|
2021-04-19 17:02:23 -07:00
|
|
|
&& verifySessionInfo(sessionInfo)) {
|
2021-05-04 11:40:05 -07:00
|
|
|
FileLog.d(LOG, "Adding package name to install queue: "
|
2021-03-25 13:51:20 -07:00
|
|
|
+ sessionInfo.getAppPackageName());
|
|
|
|
|
|
2020-08-12 13:59:27 -07:00
|
|
|
ItemInstallQueue.INSTANCE.get(mAppContext)
|
|
|
|
|
.queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
|
2020-07-29 16:54:20 -07:00
|
|
|
|
2020-10-23 09:26:44 -07:00
|
|
|
getPromiseIconIds().add(sessionInfo.getSessionId());
|
2019-09-20 12:51:37 -07:00
|
|
|
updatePromiseIconPrefs();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 17:02:23 -07:00
|
|
|
public boolean verifySessionInfo(PackageInstaller.SessionInfo sessionInfo) {
|
|
|
|
|
boolean validSessionInfo = verify(sessionInfo) != null
|
|
|
|
|
&& sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
|
|
|
|
|
&& sessionInfo.getAppIcon() != null
|
|
|
|
|
&& !TextUtils.isEmpty(sessionInfo.getAppLabel())
|
|
|
|
|
&& !promiseIconAddedForId(sessionInfo.getSessionId())
|
|
|
|
|
&& !new PackageManagerHelper(mAppContext).isAppInstalled(
|
|
|
|
|
sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
|
|
|
|
|
|
|
|
|
|
if (sessionInfo != null) {
|
|
|
|
|
Bitmap appIcon = sessionInfo.getAppIcon();
|
|
|
|
|
|
2021-05-04 11:40:05 -07:00
|
|
|
FileLog.d(LOG, String.format(
|
2021-04-19 17:02:23 -07:00
|
|
|
"Verifying session info. Valid: %b, Session verified: %b, Install reason valid:"
|
|
|
|
|
+ " %b, App icon: %s, App label: %s, Promise icon added: %b, "
|
|
|
|
|
+ "App installed: %b.",
|
|
|
|
|
validSessionInfo,
|
|
|
|
|
verify(sessionInfo) != null,
|
|
|
|
|
sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER,
|
|
|
|
|
appIcon == null ? "null" : IOUtils.toBase64String(appIcon),
|
|
|
|
|
sessionInfo.getAppLabel(),
|
|
|
|
|
promiseIconAddedForId(sessionInfo.getSessionId()),
|
|
|
|
|
new PackageManagerHelper(mAppContext).isAppInstalled(
|
|
|
|
|
sessionInfo.getAppPackageName(), getUserHandle(sessionInfo))));
|
|
|
|
|
} else {
|
2021-05-04 11:40:05 -07:00
|
|
|
FileLog.d(LOG, "Verifying session info failed: session info null.");
|
2021-04-19 17:02:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return validSessionInfo;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 09:26:44 -07:00
|
|
|
public InstallSessionTracker registerInstallTracker(InstallSessionTracker.Callback callback) {
|
2019-09-20 12:51:37 -07:00
|
|
|
InstallSessionTracker tracker = new InstallSessionTracker(this, callback);
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
2020-10-23 09:26:44 -07:00
|
|
|
mInstaller.registerSessionCallback(tracker, MODEL_EXECUTOR.getHandler());
|
2019-09-20 12:51:37 -07:00
|
|
|
} else {
|
2020-10-23 09:26:44 -07:00
|
|
|
mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, tracker);
|
2019-09-20 12:51:37 -07:00
|
|
|
}
|
|
|
|
|
return tracker;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void unregister(InstallSessionTracker tracker) {
|
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
|
|
|
mInstaller.unregisterSessionCallback(tracker);
|
|
|
|
|
} else {
|
|
|
|
|
mLauncherApps.unregisterPackageInstallerSessionCallback(tracker);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-23 09:26:44 -07:00
|
|
|
|
|
|
|
|
public static UserHandle getUserHandle(SessionInfo info) {
|
|
|
|
|
return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
|
|
|
|
|
}
|
2019-09-20 12:51:37 -07:00
|
|
|
}
|