diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java index a886c0aa09..93f0538bd5 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java @@ -374,7 +374,7 @@ public abstract class BaseIconCache { * Adds a default package entry in the cache. This entry is not persisted and will be removed * when the cache is flushed. */ - public synchronized void cachePackageInstallInfo(String packageName, UserHandle user, + protected synchronized void cachePackageInstallInfo(String packageName, UserHandle user, Bitmap icon, CharSequence title) { removeFromMemCacheLocked(packageName, user); diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java index 42a4f5cc99..a1a456149f 100644 --- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java @@ -5,8 +5,7 @@ import static org.junit.Assert.assertEquals; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.compat.PackageInstallerCompat; -import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; +import com.android.launcher3.pm.PackageInstallInfo; import org.junit.Before; import org.junit.Test; @@ -28,7 +27,7 @@ public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestC } private PackageInstallStateChangedTask newTask(String pkg, int progress) { - int state = PackageInstallerCompat.STATUS_INSTALLING; + int state = PackageInstallInfo.STATUS_INSTALLING; PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress, android.os.Process.myUserHandle()); return new PackageInstallStateChangedTask(installInfo); diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index db94bdb8c4..efb3d36bf1 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -17,6 +17,7 @@ package com.android.launcher3; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; import android.content.ComponentName; @@ -28,12 +29,13 @@ import android.os.Handler; import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.notification.NotificationListener; +import com.android.launcher3.pm.InstallSessionTracker; +import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.SecureSettingsObserver; @@ -54,6 +56,8 @@ public class LauncherAppState { private final InvariantDeviceProfile mInvariantDeviceProfile; private final SecureSettingsObserver mNotificationDotsObserver; + private final InstallSessionTracker mInstallSessionTracker; + public static LauncherAppState getInstance(final Context context) { return INSTANCE.get(context); } @@ -102,6 +106,9 @@ public class LauncherAppState { mInvariantDeviceProfile.addOnChangeListener(this::onIdpChanged); new Handler().post( () -> mInvariantDeviceProfile.verifyConfigChangedInBackground(context)); + mInstallSessionTracker = PackageInstallerCompat.getInstance(context) + .registerInstallTracker(mModel, MODEL_EXECUTOR); + if (!mContext.getResources().getBoolean(R.bool.notification_dots_enabled)) { mNotificationDotsObserver = null; } else { @@ -141,7 +148,7 @@ public class LauncherAppState { mContext.unregisterReceiver(mModel); final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext); launcherApps.removeOnAppsChangedCallback(mModel); - PackageInstallerCompat.getInstance(mContext).onStop(); + mInstallSessionTracker.unregister(); if (mNotificationDotsObserver != null) { mNotificationDotsObserver.unregister(); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index c0cf135441..f360325194 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -24,8 +24,8 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageInstaller; import android.content.pm.ShortcutInfo; -import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -34,8 +34,8 @@ import android.util.Pair; import androidx.annotation.Nullable; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; @@ -52,6 +52,8 @@ import com.android.launcher3.model.PackageInstallStateChangedTask; import com.android.launcher3.model.PackageUpdatedTask; import com.android.launcher3.model.ShortcutsChangedTask; import com.android.launcher3.model.UserLockStateChangedTask; +import com.android.launcher3.pm.InstallSessionTracker; +import com.android.launcher3.pm.PackageInstallInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; @@ -75,7 +77,7 @@ import java.util.function.Supplier; * for the Launcher. */ public class LauncherModel extends BroadcastReceiver - implements LauncherAppsCompat.OnAppsChangedCallbackCompat { + implements LauncherAppsCompat.OnAppsChangedCallbackCompat, InstallSessionTracker.Callback { private static final boolean DEBUG_RECEIVER = false; static final String TAG = "Launcher.Model"; @@ -127,20 +129,6 @@ public class LauncherModel extends BroadcastReceiver mBgAllAppsList = new AllAppsList(iconCache, appFilter); } - public void setPackageState(PackageInstallInfo installInfo) { - enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); - } - - /** - * Updates the icons and label of all pending icons for the provided package name. - */ - public void updateSessionDisplayInfo(final String packageName) { - HashSet packages = new HashSet<>(); - packages.add(packageName); - enqueueModelUpdateTask(new CacheDataUpdatedTask( - CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages)); - } - /** * Adds the provided items to the workspace. */ @@ -173,30 +161,6 @@ public class LauncherModel extends BroadcastReceiver enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); } - public void onSessionFailure(String packageName, UserHandle user) { - enqueueModelUpdateTask(new BaseModelUpdateTask() { - @Override - public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - final IntSparseArrayMap removedIds = new IntSparseArrayMap<>(); - synchronized (dataModel) { - for (ItemInfo info : dataModel.itemsIdMap) { - if (info instanceof WorkspaceItemInfo - && ((WorkspaceItemInfo) info).hasPromiseIconUi() - && user.equals(info.user) - && info.getIntent() != null - && TextUtils.equals(packageName, info.getIntent().getPackage())) { - removedIds.put(info.id, true /* remove */); - } - } - } - - if (!removedIds.isEmpty()) { - deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false)); - } - } - }); - } - @Override public void onPackageRemoved(String packageName, UserHandle user) { onPackagesRemoved(user, packageName); @@ -392,16 +356,65 @@ public class LauncherModel extends BroadcastReceiver } } + @Override public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) { + if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { + enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + apps.addPromiseApp(app.getContext(), sessionInfo); + bindApplicationsIfNeeded(); + } + }); + } + } + + @Override + public void onSessionFailure(String packageName, UserHandle user) { + if (!FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()) { + return; + } enqueueModelUpdateTask(new BaseModelUpdateTask() { @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - apps.addPromiseApp(app.getContext(), sessionInfo); - bindApplicationsIfNeeded(); + final IntSparseArrayMap removedIds = new IntSparseArrayMap<>(); + synchronized (dataModel) { + for (ItemInfo info : dataModel.itemsIdMap) { + if (info instanceof WorkspaceItemInfo + && ((WorkspaceItemInfo) info).hasPromiseIconUi() + && user.equals(info.user) + && info.getIntent() != null + && TextUtils.equals(packageName, info.getIntent().getPackage())) { + removedIds.put(info.id, true /* remove */); + } + } + } + + if (!removedIds.isEmpty()) { + deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false)); + } } }); } + @Override + public void onPackageStateChanged(PackageInstallInfo installInfo) { + enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); + } + + /** + * Updates the icons and label of all pending icons for the provided package name. + */ + @Override + public void onUpdateSessionDisplay(PackageUserKey key, PackageInstaller.SessionInfo info) { + mApp.getIconCache().updateSessionCache(key, info); + + HashSet packages = new HashSet<>(); + packages.add(key.mPackageName); + enqueueModelUpdateTask(new CacheDataUpdatedTask( + CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages)); + } + public class LoaderTransaction implements AutoCloseable { private final LoaderTask mTask; diff --git a/src/com/android/launcher3/PromiseAppInfo.java b/src/com/android/launcher3/PromiseAppInfo.java index 4ad0b3d16e..e55e4bd651 100644 --- a/src/com/android/launcher3/PromiseAppInfo.java +++ b/src/com/android/launcher3/PromiseAppInfo.java @@ -19,16 +19,16 @@ package com.android.launcher3; import android.content.Context; import android.content.Intent; -import com.android.launcher3.compat.PackageInstallerCompat; -import com.android.launcher3.util.PackageManagerHelper; - import androidx.annotation.NonNull; +import com.android.launcher3.pm.PackageInstallInfo; +import com.android.launcher3.util.PackageManagerHelper; + public class PromiseAppInfo extends AppInfo { public int level = 0; - public PromiseAppInfo(@NonNull PackageInstallerCompat.PackageInstallInfo installInfo) { + public PromiseAppInfo(@NonNull PackageInstallInfo installInfo) { componentName = installInfo.componentName; intent = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_LAUNCHER) diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index 6853bf6941..55402dd59b 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.pm.PackageInstallerCompat.getUserHandle; + import android.annotation.TargetApi; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -38,13 +40,11 @@ import android.text.TextUtils; import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.util.Executors; -import com.android.launcher3.compat.PackageInstallerCompat; import java.util.List; -import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle; - /** * BroadcastReceiver to handle session commit intent. */ diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index cdb5c4d2b7..047346dcaf 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; -import android.content.pm.PackageInstaller; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Bundle; @@ -59,9 +58,7 @@ public abstract class LauncherAppsCompat { public static LauncherAppsCompat getInstance(Context context) { synchronized (sInstanceLock) { if (sInstance == null) { - if (Utilities.ATLEAST_Q) { - sInstance = new LauncherAppsCompatVQ(context.getApplicationContext()); - } else if (Utilities.ATLEAST_OREO) { + if (Utilities.ATLEAST_OREO) { sInstance = new LauncherAppsCompatVO(context.getApplicationContext()); } else { sInstance = new LauncherAppsCompatVL(context.getApplicationContext()); @@ -88,6 +85,4 @@ public abstract class LauncherAppsCompat { UserHandle user); public abstract List getCustomShortcutActivityList( @Nullable PackageUserKey packageUser); - - public abstract List getAllPackageInstallerSessions(); } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java index 1885d8f03d..f1b9756e4a 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; -import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; @@ -33,6 +32,9 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.PackageUserKey; @@ -41,9 +43,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - public class LauncherAppsCompatVL extends LauncherAppsCompat { protected final LauncherApps mLauncherApps; @@ -207,10 +206,5 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { } return result; } - - @Override - public List getAllPackageInstallerSessions() { - return mContext.getPackageManager().getPackageInstaller().getAllSessions(); - } } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java deleted file mode 100644 index 0a1811e341..0000000000 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2019 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 android.annotation.TargetApi; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInstaller; - -import java.util.List; - -@TargetApi(29) -public class LauncherAppsCompatVQ extends LauncherAppsCompatVO { - - LauncherAppsCompatVQ(Context context) { - super(context); - } - - public List getAllPackageInstallerSessions() { - return mLauncherApps.getAllPackageInstallerSessions(); - } -} diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java deleted file mode 100644 index 55df98b48f..0000000000 --- a/src/com/android/launcher3/compat/PackageInstallerCompat.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageInstaller; -import android.content.pm.PackageInstaller.SessionInfo; -import android.os.Process; -import android.os.UserHandle; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -import androidx.annotation.NonNull; - -import com.android.launcher3.Utilities; -import com.android.launcher3.util.PackageUserKey; - -public abstract class PackageInstallerCompat { - - // Set 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"; - - public static final int STATUS_INSTALLED = 0; - public static final int STATUS_INSTALLING = 1; - public static final int STATUS_FAILED = 2; - - private static final Object sInstanceLock = new Object(); - private static PackageInstallerCompat sInstance; - - public static PackageInstallerCompat getInstance(Context context) { - synchronized (sInstanceLock) { - if (sInstance == null) { - sInstance = new PackageInstallerCompatVL(context); - } - return sInstance; - } - } - - public static UserHandle getUserHandle(SessionInfo info) { - return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle(); - } - - /** - * @return a map of active installs to their progress - */ - public abstract HashMap updateAndGetActiveSessionCache(); - - /** - * @return an active SessionInfo for {@param pkg} or null if none exists. - */ - public abstract SessionInfo getActiveSessionInfo(UserHandle user, String pkg); - - public abstract void onStop(); - - public static final class PackageInstallInfo { - public final ComponentName componentName; - public final String packageName; - public final int state; - public final int progress; - public final UserHandle user; - - private PackageInstallInfo(@NonNull SessionInfo info) { - this.state = STATUS_INSTALLING; - this.packageName = info.getAppPackageName(); - this.componentName = new ComponentName(packageName, ""); - this.progress = (int) (info.getProgress() * 100f); - this.user = getUserHandle(info); - } - - public PackageInstallInfo(String packageName, int state, int progress, UserHandle user) { - this.state = state; - this.packageName = packageName; - this.componentName = new ComponentName(packageName, ""); - this.progress = progress; - this.user = user; - } - - public static PackageInstallInfo fromInstallingState(SessionInfo info) { - return new PackageInstallInfo(info); - } - - public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) { - return new PackageInstallInfo(packageName, state, 0 /* progress */, user); - } - - } - - public abstract List getAllVerifiedSessions(); - - /** - * Returns true if a promise icon was already added to the home screen for {@param sessionId}. - * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS. - */ - public abstract boolean promiseIconAddedForId(int sessionId); - - /** - * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS. - */ - public abstract void removePromiseIconId(int sessionId); -} diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java deleted file mode 100644 index f421f7c016..0000000000 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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.Utilities.getPrefs; -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.LauncherAppState; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.SessionCommitReceiver; -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.icons.IconCache; -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; - -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(); - mInstaller.registerSessionCallback(mCallback, MODEL_EXECUTOR.getHandler()); - mLauncherApps = LauncherAppsCompat.getInstance(context); - 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() { - mInstaller.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.PROMISE_APPS_IN_ALL_APPS.get() && 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) { - mActiveSessions.put(session.getSessionId(), - new PackageUserKey(session.getAppPackageName(), getUserHandle(session))); - addSessionInfoToCache(session, getUserHandle(session)); - LauncherAppState app = LauncherAppState.getInstanceNoCreate(); - if (app != null) { - app.getModel().updateSessionDisplayInfo(session.getAppPackageName()); - } - 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(); - } -} diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 11c7f20537..c0a095d57d 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ShortcutInfo; @@ -52,6 +53,7 @@ import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.InstantAppResolver; +import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import java.util.function.Supplier; @@ -247,6 +249,10 @@ public class IconCache extends BaseIconCache { return mIconProvider.getIcon(info, mIconDpi, flattenDrawable); } + public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) { + cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel()); + } + @Override protected String getIconSystemState(String packageName) { return mIconProvider.getSystemStateForPackage(mSystemState, packageName) diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index dfd5a708bf..844a2a61a1 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -29,11 +29,11 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.LauncherSettings; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.PackageManagerHelper; diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java index 3873a17e03..1e1df88d39 100644 --- a/src/com/android/launcher3/model/AllAppsList.java +++ b/src/com/android/launcher3/model/AllAppsList.java @@ -28,14 +28,16 @@ import android.os.Process; import android.os.UserHandle; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.launcher3.AppFilter; import com.android.launcher3.AppInfo; import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.PackageInstallerCompat; -import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.icons.IconCache; +import com.android.launcher3.pm.PackageInstallInfo; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.SafeCloseable; @@ -46,9 +48,6 @@ import java.util.HashSet; import java.util.List; import java.util.function.Consumer; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - /** * Stores the list of all applications for the all apps view. @@ -110,8 +109,7 @@ public class AllAppsList { mDataChanged = true; } - public void addPromiseApp(Context context, - PackageInstallerCompat.PackageInstallInfo installInfo) { + public void addPromiseApp(Context context, PackageInstallInfo installInfo) { ApplicationInfo applicationInfo = LauncherAppsCompat.getInstance(context) .getApplicationInfo(installInfo.packageName, 0, installInfo.user); // only if not yet installed @@ -134,10 +132,10 @@ public class AllAppsList { && appInfo.user.equals(user) && appInfo instanceof PromiseAppInfo) { final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo; - if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLING) { + if (installInfo.state == PackageInstallInfo.STATUS_INSTALLING) { promiseAppInfo.level = installInfo.progress; return promiseAppInfo; - } else if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) { + } else if (installInfo.state == PackageInstallInfo.STATUS_FAILED) { removeApp(i); } } diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java index 783e908e30..ac44b0e0bb 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTask.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java @@ -17,6 +17,8 @@ import android.graphics.Point; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.VisibleForTesting; + import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; @@ -27,21 +29,17 @@ import com.android.launcher3.LauncherSettings.Settings; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.compat.AppWidgetManagerCompat; -import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.provider.LauncherDbUtils; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; -import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.function.Consumer; - -import androidx.annotation.VisibleForTesting; /** * This class takes care of shrinking the workspace (by maximum of one row and one column), as a @@ -973,7 +971,7 @@ public class GridSizeMigrationTask { validPackages.add(info.packageName); } PackageInstallerCompat.getInstance(context) - .updateAndGetActiveSessionCache().keySet() + .getActiveSessions().keySet() .forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName)); return validPackages; } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index ad6b5c40e6..4c88eb73fd 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -53,7 +53,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; @@ -66,6 +65,8 @@ import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.ShortcutCachingLogic; import com.android.launcher3.icons.cache.IconCacheUpdateHandler; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.pm.PackageInstallInfo; +import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -297,7 +298,9 @@ public class LoaderTask implements Runnable { mBgDataModel.clear(); final HashMap installingPkgs = - mPackageInstaller.updateAndGetActiveSessionCache(); + mPackageInstaller.getActiveSessions(); + installingPkgs.forEach(mApp.getIconCache()::updateSessionCache); + final PackageUserKey tempPackageKey = new PackageUserKey(null, null); mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs); @@ -856,7 +859,7 @@ public class LoaderTask implements Runnable { for (PackageInstaller.SessionInfo info : mPackageInstaller.getAllVerifiedSessions()) { mBgAllAppsList.addPromiseApp(mApp.getContext(), - PackageInstallerCompat.PackageInstallInfo.fromInstallingState(info)); + PackageInstallInfo.fromInstallingState(info)); } } diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index 802cbc7c5e..2832150d23 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -19,16 +19,14 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.compat.PackageInstallerCompat; -import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; +import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.pm.PackageInstallInfo; import com.android.launcher3.util.InstantAppResolver; import java.util.HashSet; @@ -46,7 +44,7 @@ public class PackageInstallStateChangedTask extends BaseModelUpdateTask { @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { + if (mInstallInfo.state == PackageInstallInfo.STATUS_INSTALLED) { try { // For instant apps we do not get package-add. Use setting events to update // any pinned icons. @@ -79,7 +77,7 @@ public class PackageInstallStateChangedTask extends BaseModelUpdateTask { if (si.hasPromiseIconUi() && (cn != null) && mInstallInfo.packageName.equals(cn.getPackageName())) { si.setInstallProgress(mInstallInfo.progress); - if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) { + if (mInstallInfo.state == PackageInstallInfo.STATUS_FAILED) { // Mark this info as broken. si.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE; } diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java new file mode 100644 index 0000000000..f157603e1a --- /dev/null +++ b/src/com/android/launcher3/pm/InstallSessionTracker.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2019 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.pm.PackageInstallInfo.STATUS_FAILED; +import static com.android.launcher3.pm.PackageInstallInfo.STATUS_INSTALLED; +import static com.android.launcher3.pm.PackageInstallerCompat.getUserHandle; + +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.os.UserHandle; +import android.util.SparseArray; + +import com.android.launcher3.util.PackageUserKey; + +public class InstallSessionTracker extends PackageInstaller.SessionCallback { + + // Lazily initialized + private SparseArray mActiveSessions = null; + + private final PackageInstallerCompat mInstallerCompat; + private final Callback mCallback; + + InstallSessionTracker(PackageInstallerCompat installerCompat, Callback callback) { + mInstallerCompat = installerCompat; + mCallback = callback; + } + + @Override + public void onCreated(int sessionId) { + SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId); + if (sessionInfo != null) { + mCallback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo)); + } + + mInstallerCompat.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. + SparseArray activeSessions = getActiveSessionMap(); + PackageUserKey key = activeSessions.get(sessionId); + activeSessions.remove(sessionId); + + if (key != null && key.mPackageName != null) { + String packageName = key.mPackageName; + PackageInstallInfo info = PackageInstallInfo.fromState( + success ? STATUS_INSTALLED : STATUS_FAILED, + packageName, key.mUser); + mCallback.onPackageStateChanged(info); + + if (!success && mInstallerCompat.promiseIconAddedForId(sessionId)) { + mCallback.onSessionFailure(packageName, key.mUser); + // If it is successful, the id is removed in the the package added flow. + mInstallerCompat.removePromiseIconId(sessionId); + } + } + } + + @Override + public void onProgressChanged(int sessionId, float progress) { + SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId); + if (session != null && session.getAppPackageName() != null) { + mCallback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(session)); + } + } + + @Override + public void onActiveChanged(int sessionId, boolean active) { } + + @Override + public void onBadgingChanged(int sessionId) { + SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId); + if (sessionInfo != null) { + mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo); + } + } + + private SessionInfo pushSessionDisplayToLauncher(int sessionId) { + SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId); + if (session != null && session.getAppPackageName() != null) { + PackageUserKey key = + new PackageUserKey(session.getAppPackageName(), getUserHandle(session)); + getActiveSessionMap().put(session.getSessionId(), key); + mCallback.onUpdateSessionDisplay(key, session); + return session; + } + return null; + } + + private SparseArray getActiveSessionMap() { + if (mActiveSessions == null) { + mActiveSessions = new SparseArray<>(); + mInstallerCompat.getActiveSessions().forEach( + (key, si) -> mActiveSessions.put(si.getSessionId(), key)); + } + return mActiveSessions; + } + + public void unregister() { + mInstallerCompat.unregister(this); + } + + public interface Callback { + + void onSessionFailure(String packageName, UserHandle user); + + void onUpdateSessionDisplay(PackageUserKey key, SessionInfo info); + + void onPackageStateChanged(PackageInstallInfo info); + + void onInstallSessionCreated(PackageInstallInfo info); + } +} diff --git a/src/com/android/launcher3/pm/PackageInstallInfo.java b/src/com/android/launcher3/pm/PackageInstallInfo.java new file mode 100644 index 0000000000..6776ec4a2a --- /dev/null +++ b/src/com/android/launcher3/pm/PackageInstallInfo.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 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 android.content.ComponentName; +import android.content.pm.PackageInstaller; +import android.os.UserHandle; + +import androidx.annotation.NonNull; + +public final class PackageInstallInfo { + + public static final int STATUS_INSTALLED = 0; + public static final int STATUS_INSTALLING = 1; + public static final int STATUS_FAILED = 2; + + public final ComponentName componentName; + public final String packageName; + public final int state; + public final int progress; + public final UserHandle user; + + private PackageInstallInfo(@NonNull PackageInstaller.SessionInfo info) { + this.state = STATUS_INSTALLING; + this.packageName = info.getAppPackageName(); + this.componentName = new ComponentName(packageName, ""); + this.progress = (int) (info.getProgress() * 100f); + this.user = PackageInstallerCompat.getUserHandle(info); + } + + public PackageInstallInfo(String packageName, int state, int progress, UserHandle user) { + this.state = state; + this.packageName = packageName; + this.componentName = new ComponentName(packageName, ""); + this.progress = progress; + this.user = user; + } + + public static PackageInstallInfo fromInstallingState(PackageInstaller.SessionInfo info) { + return new PackageInstallInfo(info); + } + + public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) { + return new PackageInstallInfo(packageName, state, 0 /* progress */, user); + } + +} diff --git a/src/com/android/launcher3/pm/PackageInstallerCompat.java b/src/com/android/launcher3/pm/PackageInstallerCompat.java new file mode 100644 index 0000000000..520c207c5f --- /dev/null +++ b/src/com/android/launcher3/pm/PackageInstallerCompat.java @@ -0,0 +1,220 @@ +/* + * 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; + +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; +import android.os.Build; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; + +import com.android.launcher3.SessionCommitReceiver; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.LooperExecutor; +import com.android.launcher3.util.PackageUserKey; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class PackageInstallerCompat { + + // Set 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 Object sInstanceLock = new Object(); + private static final boolean DEBUG = false; + private static PackageInstallerCompat sInstance; + private final LauncherApps mLauncherApps; + private final Context mAppContext; + private final IntSet mPromiseIconIds; + + private final PackageInstaller mInstaller; + private final HashMap mSessionVerifiedMap = new HashMap<>(); + + public PackageInstallerCompat(Context context) { + mInstaller = context.getPackageManager().getPackageInstaller(); + mAppContext = context.getApplicationContext(); + mLauncherApps = context.getSystemService(LauncherApps.class); + + mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString( + getPrefs(context).getString(PROMISE_ICON_IDS, ""))); + + cleanUpPromiseIconIds(); + } + + public static PackageInstallerCompat getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + sInstance = new PackageInstallerCompat(context); + } + return sInstance; + } + } + + public static UserHandle getUserHandle(SessionInfo info) { + return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle(); + } + + protected void cleanUpPromiseIconIds() { + 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)); + } + } + + public HashMap getActiveSessions() { + HashMap activePackages = new HashMap<>(); + for (SessionInfo info : getAllVerifiedSessions()) { + activePackages.put( + new PackageUserKey(info.getAppPackageName(), getUserHandle(info)), 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; + } + + private void updatePromiseIconPrefs() { + getPrefs(mAppContext).edit() + .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString()) + .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)) { + 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; + } + + 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; + } + + public boolean promiseIconAddedForId(int sessionId) { + return mPromiseIconIds.contains(sessionId); + } + + public void removePromiseIconId(int sessionId) { + if (mPromiseIconIds.contains(sessionId)) { + mPromiseIconIds.getArray().removeValue(sessionId); + 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 + */ + void tryQueuePromiseAppIcon(PackageInstaller.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()) + && LauncherAppsCompat.getInstance(mAppContext).getApplicationInfo( + sessionInfo.getAppPackageName(), 0, getUserHandle(sessionInfo)) == null) { + SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo); + mPromiseIconIds.add(sessionInfo.getSessionId()); + updatePromiseIconPrefs(); + } + } + + public InstallSessionTracker registerInstallTracker( + InstallSessionTracker.Callback callback, LooperExecutor executor) { + InstallSessionTracker tracker = new InstallSessionTracker(this, callback); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + mInstaller.registerSessionCallback(tracker, executor.getHandler()); + } else { + mLauncherApps.registerPackageInstallerSessionCallback(executor, tracker); + } + return tracker; + } + + void unregister(InstallSessionTracker tracker) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + mInstaller.unregisterSessionCallback(tracker); + } else { + mLauncherApps.unregisterPackageInstallerSessionCallback(tracker); + } + } +} diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index 03493a5389..455af5a449 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -25,8 +25,6 @@ import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET; import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS; import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller.SessionInfo; @@ -52,9 +50,9 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.AppWidgetManagerCompat; -import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.views.FloatingIconView; import com.android.launcher3.widget.PendingAppWidgetHostView; diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index ac87148633..3d691da092 100644 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -42,7 +42,7 @@ import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.LauncherSettings; import com.android.launcher3.compat.AppWidgetManagerCompat; -import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; @@ -302,7 +302,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { Set activePackage = getOnUiThread(() -> { Set packages = new HashSet<>(); - PackageInstallerCompat.getInstance(mTargetContext).updateAndGetActiveSessionCache() + PackageInstallerCompat.getInstance(mTargetContext).getActiveSessions() .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName)); return packages; });