/* * 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.compat; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.os.UserHandle; import android.text.TextUtils; import android.util.SparseArray; import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.Utilities; import com.android.launcher3.icons.IconCache; import com.android.launcher3.LauncherAppState; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Thunk; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import static com.android.launcher3.Utilities.getPrefs; public class PackageInstallerCompatVL extends PackageInstallerCompat { private static final boolean DEBUG = false; @Thunk final SparseArray mActiveSessions = new SparseArray<>(); @Thunk final PackageInstaller mInstaller; private final IconCache mCache; private final Context mAppContext; private final HashMap mSessionVerifiedMap = new HashMap<>(); private final LauncherAppsCompat mLauncherApps; private final IntSet mPromiseIconIds; PackageInstallerCompatVL(Context context) { mAppContext = context.getApplicationContext(); mInstaller = context.getPackageManager().getPackageInstaller(); mCache = LauncherAppState.getInstance(context).getIconCache(); mLauncherApps = LauncherAppsCompat.getInstance(context); mLauncherApps.registerSessionCallback(MODEL_EXECUTOR, mCallback); mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString( getPrefs(context).getString(PROMISE_ICON_IDS, ""))); cleanUpPromiseIconIds(); } private void cleanUpPromiseIconIds() { IntArray existingIds = new IntArray(); for (SessionInfo info : updateAndGetActiveSessionCache().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)); } } @Override public HashMap updateAndGetActiveSessionCache() { HashMap activePackages = new HashMap<>(); for (SessionInfo info : getAllVerifiedSessions()) { addSessionInfoToCache(info, getUserHandle(info)); if (info.getAppPackageName() != null) { activePackages.put(new PackageUserKey(info.getAppPackageName(), getUserHandle(info)), info); mActiveSessions.put(info.getSessionId(), new PackageUserKey(info.getAppPackageName(), getUserHandle(info))); } } 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; } @Thunk void addSessionInfoToCache(SessionInfo info, UserHandle user) { String packageName = info.getAppPackageName(); if (packageName != null) { mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(), info.getAppLabel()); } } @Override public void onStop() { mLauncherApps.unregisterSessionCallback(mCallback); } @Thunk void sendUpdate(PackageInstallInfo info) { LauncherAppState app = LauncherAppState.getInstanceNoCreate(); if (app != null) { app.getModel().setPackageState(info); } } /** * 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 */ private void tryQueuePromiseAppIcon(SessionInfo sessionInfo) { if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get() && SessionCommitReceiver.isEnabled(mAppContext) && verify(sessionInfo) != null && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER && sessionInfo.getAppIcon() != null && !TextUtils.isEmpty(sessionInfo.getAppLabel()) && !mPromiseIconIds.contains(sessionInfo.getSessionId()) && mLauncherApps.getApplicationInfo(sessionInfo.getAppPackageName(), 0, getUserHandle(sessionInfo)) == null) { SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo); mPromiseIconIds.add(sessionInfo.getSessionId()); updatePromiseIconPrefs(); } } private final SessionCallback mCallback = new SessionCallback() { @Override public void onCreated(int sessionId) { SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId); if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS && sessionInfo != null) { LauncherAppState app = LauncherAppState.getInstanceNoCreate(); if (app != null) { app.getModel().onInstallSessionCreated( PackageInstallInfo.fromInstallingState(sessionInfo)); } } tryQueuePromiseAppIcon(sessionInfo); } @Override public void onFinished(int sessionId, boolean success) { // For a finished session, we can't get the session info. So use the // packageName from our local cache. PackageUserKey key = mActiveSessions.get(sessionId); mActiveSessions.remove(sessionId); if (key != null && key.mPackageName != null) { String packageName = key.mPackageName; sendUpdate(PackageInstallInfo.fromState(success ? STATUS_INSTALLED : STATUS_FAILED, packageName, key.mUser)); if (!success && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get() && mPromiseIconIds.contains(sessionId)) { LauncherAppState appState = LauncherAppState.getInstanceNoCreate(); if (appState != null) { appState.getModel().onSessionFailure(packageName, key.mUser); } // If it is successful, the id is removed in the the package added flow. removePromiseIconId(sessionId); } } } @Override public void onProgressChanged(int sessionId, float progress) { SessionInfo session = verify(mInstaller.getSessionInfo(sessionId)); if (session != null && session.getAppPackageName() != null) { sendUpdate(PackageInstallInfo.fromInstallingState(session)); } } @Override public void onActiveChanged(int sessionId, boolean active) { } @Override public void onBadgingChanged(int sessionId) { SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId); if (sessionInfo != null) { tryQueuePromiseAppIcon(sessionInfo); } } private SessionInfo pushSessionDisplayToLauncher(int sessionId) { SessionInfo session = verify(mInstaller.getSessionInfo(sessionId)); if (session != null && session.getAppPackageName() != null) { UserHandle user = getUserHandle(session); mActiveSessions.put(session.getSessionId(), new PackageUserKey(session.getAppPackageName(), user)); addSessionInfoToCache(session, user); LauncherAppState app = LauncherAppState.getInstanceNoCreate(); if (app != null) { app.getModel().updateSessionDisplayInfo(session.getAppPackageName(), user); } return session; } return null; } }; private PackageInstaller.SessionInfo verify(PackageInstaller.SessionInfo sessionInfo) { if (sessionInfo == null || sessionInfo.getInstallerPackageName() == null || TextUtils.isEmpty(sessionInfo.getAppPackageName())) { return null; } String pkg = sessionInfo.getInstallerPackageName(); synchronized (mSessionVerifiedMap) { if (!mSessionVerifiedMap.containsKey(pkg)) { LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mAppContext); boolean hasSystemFlag = launcherApps.getApplicationInfo(pkg, ApplicationInfo.FLAG_SYSTEM, getUserHandle(sessionInfo)) != null; mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag); } } return mSessionVerifiedMap.get(pkg) ? sessionInfo : null; } @Override public List getAllVerifiedSessions() { List list = new ArrayList<>(Utilities.ATLEAST_Q ? mLauncherApps.getAllPackageInstallerSessions() : mInstaller.getAllSessions()); Iterator it = list.iterator(); while (it.hasNext()) { if (verify(it.next()) == null) { it.remove(); } } return list; } @Override public boolean promiseIconAddedForId(int sessionId) { return mPromiseIconIds.contains(sessionId); } @Override public void removePromiseIconId(int sessionId) { if (mPromiseIconIds.contains(sessionId)) { mPromiseIconIds.getArray().removeValue(sessionId); updatePromiseIconPrefs(); } } private void updatePromiseIconPrefs() { getPrefs(mAppContext).edit() .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString()) .apply(); } }