From 817afa34472dba49b1dee0489da11f410ff09fcd Mon Sep 17 00:00:00 2001 From: Mario Bertschler Date: Wed, 15 Mar 2017 11:56:47 -0700 Subject: [PATCH] Show promise app icon in All Apps while installation process. This CL only modifies the model and is behind a feature flag which per default is set to false. The app icon will appear as a promise icon, it reacts on icon or label changes and the icon will be remove on finishing the installation process. With this CL the progress of the installation process is not visible. Bug: 23952570 Change-Id: I510825d0b0b1b01eb14f7e50f0a2358b0d8b99b5 --- src/com/android/launcher3/AllAppsList.java | 56 ++++++++------- src/com/android/launcher3/IconCache.java | 3 +- src/com/android/launcher3/LauncherModel.java | 32 +++++++++ src/com/android/launcher3/PromiseAppInfo.java | 47 +++++++++++++ .../compat/PackageInstallerCompat.java | 31 +++++++-- .../compat/PackageInstallerCompatVL.java | 69 +++++++++++++++---- .../model/PackageInstallStateChangedTask.java | 44 +++++++++++- .../launcher3/config/FeatureFlags.java | 3 +- .../PackageInstallStateChangedTaskTest.java | 5 +- 9 files changed, 242 insertions(+), 48 deletions(-) create mode 100644 src/com/android/launcher3/PromiseAppInfo.java diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 5b42cad963..d7f0180fa2 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -18,10 +18,15 @@ package com.android.launcher3; import android.content.ComponentName; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.os.Process; import android.os.UserHandle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; @@ -69,7 +74,7 @@ public class AllAppsList { if (!mAppFilter.shouldShowApp(info.componentName)) { return; } - if (findActivity(data, info.componentName, info.user)) { + if (findAppInfo(info.componentName, info.user) != null) { return; } mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */); @@ -78,6 +83,25 @@ public class AllAppsList { added.add(info); } + public void addPromiseApp(Context context, + PackageInstallerCompat.PackageInstallInfo installInfo) { + ApplicationInfo applicationInfo = LauncherAppsCompat.getInstance(context) + .getApplicationInfo(installInfo.packageName, 0, Process.myUserHandle()); + // only if not yet installed + if (applicationInfo == null) { + PromiseAppInfo info = new PromiseAppInfo(installInfo); + mIconCache.getTitleAndIcon(info, info.usingLowResIcon); + data.add(info); + added.add(info); + } + } + + public void removePromiseApp(AppInfo appInfo) { + // the removed list is handled by the caller + // so not adding it here + data.remove(appInfo); + } + public void clear() { data.clear(); // TODO: do we clear these too? @@ -169,9 +193,7 @@ public class AllAppsList { // Find enabled activities and add them to the adapter // Also updates existing activities with new labels/icons for (final LauncherActivityInfo info : matches) { - AppInfo applicationInfo = findApplicationInfoLocked( - info.getComponentName().getPackageName(), user, - info.getComponentName().getClassName()); + AppInfo applicationInfo = findAppInfo(info.getComponentName(), user); if (applicationInfo == null) { add(new AppInfo(context, info, user), info); } else { @@ -208,28 +230,14 @@ public class AllAppsList { } /** - * Returns whether apps contains component. + * Find an AppInfo object for the given componentName + * + * @return the corresponding AppInfo or null */ - private static boolean findActivity(ArrayList apps, ComponentName component, - UserHandle user) { - final int N = apps.size(); - for (int i = 0; i < N; i++) { - final AppInfo info = apps.get(i); - if (info.user.equals(user) && info.componentName.equals(component)) { - return true; - } - } - return false; - } - - /** - * Find an ApplicationInfo object for the given packageName and className. - */ - private AppInfo findApplicationInfoLocked(String packageName, UserHandle user, - String className) { + private @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName, + @NonNull UserHandle user) { for (AppInfo info: data) { - if (user.equals(info.user) && packageName.equals(info.componentName.getPackageName()) - && className.equals(info.componentName.getClassName())) { + if (componentName.equals(info.componentName) && user.equals(info.user)) { return info; } } diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 1f473a26f6..a5552aafdc 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -437,9 +437,10 @@ public class IconCache { * Updates {@param application} only if a valid entry is found. */ public synchronized void updateTitleAndIcon(AppInfo application) { + boolean usePackageIcon = application instanceof PromiseAppInfo; CacheEntry entry = cacheLocked(application.componentName, Provider.of(null), - application.user, false, application.usingLowResIcon); + application.user, usePackageIcon, application.usingLowResIcon); if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) { applyCacheEntry(entry, application); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 35811d38a2..707ec8642d 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -25,7 +25,9 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInstaller; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; @@ -573,6 +575,25 @@ public class LauncherModel extends BroadcastReceiver screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK)); } + public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) { + enqueueModelUpdateTask(new ExtendedModelTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + apps.addPromiseApp(app.getContext(), sessionInfo); + if (!apps.added.isEmpty()) { + final ArrayList arrayList = new ArrayList<>(apps.added); + apps.added.clear(); + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAppsAdded(null, null, null, arrayList); + } + }); + } + } + }); + } + /** * Runnable for the thread that loads the contents of the launcher: * - workspace icons @@ -1691,10 +1712,21 @@ public class LauncherModel extends BroadcastReceiver }); } } + + if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) { + // get all active sessions and add them to the all apps list + PackageInstallerCompat installer = PackageInstallerCompat.getInstance(mContext); + for (PackageInstaller.SessionInfo info : installer.getAllVerifiedSessions()) { + mBgAllAppsList.addPromiseApp(mContext, + PackageInstallInfo.fromInstallingState(info)); + } + } + // Huh? Shouldn't this be inside the Runnable below? final ArrayList added = mBgAllAppsList.added; mBgAllAppsList.added = new ArrayList(); + // Post callback on main thread mUiExecutor.execute(new Runnable() { public void run() { diff --git a/src/com/android/launcher3/PromiseAppInfo.java b/src/com/android/launcher3/PromiseAppInfo.java new file mode 100644 index 0000000000..04ba1d366f --- /dev/null +++ b/src/com/android/launcher3/PromiseAppInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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; + +import android.content.Intent; +import android.support.annotation.NonNull; + +import com.android.launcher3.compat.PackageInstallerCompat; + +public class PromiseAppInfo extends AppInfo { + + public int level = 0; + + public PromiseAppInfo(@NonNull PackageInstallerCompat.PackageInstallInfo installInfo) { + componentName = installInfo.componentName; + intent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(componentName) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + } + + @Override + public ShortcutInfo makeShortcut() { + ShortcutInfo shortcut = new ShortcutInfo(this); + shortcut.setInstallProgress(level); + // We need to update the component name when the apk is installed + shortcut.status |= ShortcutInfo.FLAG_AUTOINTALL_ICON; + // Since the user is manually placing it on homescreen, it should not be auto-removed later + shortcut.status |= ShortcutInfo.FLAG_RESTORE_STARTED; + return shortcut; + } +} diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java index c7fe0cec83..112cca5402 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompat.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java @@ -16,9 +16,13 @@ package com.android.launcher3.compat; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageInstaller; +import android.support.annotation.NonNull; import java.util.HashMap; +import java.util.List; public abstract class PackageInstallerCompat { @@ -46,19 +50,34 @@ public abstract class PackageInstallerCompat { 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 int state; - public int progress; - - public PackageInstallInfo(String packageName) { - this.packageName = packageName; + 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); } public PackageInstallInfo(String packageName, int state, int progress) { - this.packageName = packageName; this.state = state; + this.packageName = packageName; + this.componentName = new ComponentName(packageName, ""); this.progress = progress; } + + public static PackageInstallInfo fromInstallingState(PackageInstaller.SessionInfo info) { + return new PackageInstallInfo(info); + } + + public static PackageInstallInfo fromState(int state, String packageName) { + return new PackageInstallInfo(packageName, state, 0 /* progress */); + } + } + + public abstract List getAllVerifiedSessions(); } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java index b87582f552..d7cd032f7b 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -17,6 +17,7 @@ package com.android.launcher3.compat; 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; @@ -28,23 +29,31 @@ import android.util.SparseArray; import com.android.launcher3.IconCache; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; +import com.android.launcher3.config.FeatureFlags; 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 Handler mWorker; + private final Context mAppContext; + private final HashMap mSessionVerifiedMap = new HashMap<>(); PackageInstallerCompatVL(Context context) { + mAppContext = context.getApplicationContext(); mInstaller = context.getPackageManager().getPackageInstaller(); mCache = LauncherAppState.getInstance(context).getIconCache(); mWorker = new Handler(LauncherModel.getWorkerLooper()); - mInstaller.registerSessionCallback(mCallback, mWorker); } @@ -52,7 +61,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { public HashMap updateAndGetActiveSessionCache() { HashMap activePackages = new HashMap<>(); UserHandle user = Process.myUserHandle(); - for (SessionInfo info : mInstaller.getAllSessions()) { + for (SessionInfo info : getAllVerifiedSessions()) { addSessionInfoToCache(info, user); if (info.getAppPackageName() != null) { activePackages.put(info.getAppPackageName(), (int) (info.getProgress() * 100)); @@ -86,7 +95,14 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { @Override public void onCreated(int sessionId) { - pushSessionDisplayToLauncher(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)); + } + } } @Override @@ -97,18 +113,17 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { mActiveSessions.remove(sessionId); if (packageName != null) { - sendUpdate(new PackageInstallInfo(packageName, - success ? STATUS_INSTALLED : STATUS_FAILED, 0)); + sendUpdate(PackageInstallInfo.fromState( + success ? STATUS_INSTALLED : STATUS_FAILED, + packageName)); } } @Override public void onProgressChanged(int sessionId, float progress) { - SessionInfo session = mInstaller.getSessionInfo(sessionId); + SessionInfo session = verify(mInstaller.getSessionInfo(sessionId)); if (session != null && session.getAppPackageName() != null) { - sendUpdate(new PackageInstallInfo(session.getAppPackageName(), - STATUS_INSTALLING, - (int) (session.getProgress() * 100))); + sendUpdate(PackageInstallInfo.fromInstallingState(session)); } } @@ -120,16 +135,46 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { pushSessionDisplayToLauncher(sessionId); } - private void pushSessionDisplayToLauncher(int sessionId) { - SessionInfo session = mInstaller.getSessionInfo(sessionId); + private SessionInfo pushSessionDisplayToLauncher(int sessionId) { + SessionInfo session = verify(mInstaller.getSessionInfo(sessionId)); if (session != null && session.getAppPackageName() != null) { + mActiveSessions.put(sessionId, session.getAppPackageName()); addSessionInfoToCache(session, Process.myUserHandle()); 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) { + return null; + } + String pkg = sessionInfo.getAppPackageName(); + synchronized (mSessionVerifiedMap) { + if (!mSessionVerifiedMap.containsKey(pkg)) { + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mAppContext); + boolean hasSystemFlag = launcherApps.getApplicationInfo(pkg, + ApplicationInfo.FLAG_SYSTEM, Process.myUserHandle()) != null; + mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag); + } + } + return mSessionVerifiedMap.get(pkg) ? sessionInfo : null; + } + + @Override + public List getAllVerifiedSessions() { + List list = new ArrayList<>(mInstaller.getAllSessions()); + Iterator it = list.iterator(); + while (it.hasNext()) { + if (verify(it.next()) == null) { + it.remove(); + } + } + return list; + } } diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index 5d04325e82..9c5f18917e 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -18,15 +18,18 @@ package com.android.launcher3.model; import android.content.ComponentName; import com.android.launcher3.AllAppsList; +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.LauncherModel.Callbacks; +import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; +import java.util.ArrayList; import java.util.HashSet; /** @@ -47,6 +50,46 @@ public class PackageInstallStateChangedTask extends ExtendedModelTask { return; } + synchronized (apps) { + final ArrayList updated = new ArrayList<>(); + final ArrayList removed = new ArrayList<>(); + for (int i=0; i < apps.size(); i++) { + final AppInfo appInfo = apps.get(i); + final ComponentName tgtComp = appInfo.getTargetComponent(); + if (tgtComp != null && tgtComp.getPackageName().equals(mInstallInfo.packageName)) { + if (appInfo instanceof PromiseAppInfo) { + final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo; + if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLING) { + promiseAppInfo.level = mInstallInfo.progress; + updated.add(appInfo); + } else if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED + || mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { + apps.removePromiseApp(appInfo); + removed.add(appInfo); + } + } + } + } + if (!updated.isEmpty()) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + // TODO: this currently causes unnecessary relayouts + // we need to introduce a new bindPromiseAppsChanged + callbacks.bindAppsUpdated(updated); + } + }); + } + if (!removed.isEmpty()) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAppInfosRemoved(removed); + } + }); + } + } + synchronized (dataModel) { final HashSet updates = new HashSet<>(); for (ItemInfo info : dataModel.itemsIdMap) { @@ -56,7 +99,6 @@ public class PackageInstallStateChangedTask extends ExtendedModelTask { if (si.isPromise() && (cn != null) && mInstallInfo.packageName.equals(cn.getPackageName())) { si.setInstallProgress(mInstallInfo.progress); - if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) { // Mark this info as broken. si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; diff --git a/src_flags/com/android/launcher3/config/FeatureFlags.java b/src_flags/com/android/launcher3/config/FeatureFlags.java index 87e987162e..9e20748120 100644 --- a/src_flags/com/android/launcher3/config/FeatureFlags.java +++ b/src_flags/com/android/launcher3/config/FeatureFlags.java @@ -36,7 +36,8 @@ public final class FeatureFlags { public static boolean LAUNCHER3_DIRECT_SCROLL = true; // When enabled while all-apps open, the soft input will be set to adjust resize . public static boolean LAUNCHER3_UPDATE_SOFT_INPUT_MODE = false; - + // When enabled the promise icon is visible in all apps while installation an app. + public static boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false; // Feature flag to enable moving the QSB on the 0th screen of the workspace. public static final boolean QSB_ON_FIRST_SCREEN = true; diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java index d6555620cb..ed893c42e4 100644 --- a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java +++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java @@ -21,9 +21,8 @@ public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestC } private PackageInstallStateChangedTask newTask(String pkg, int progress) { - PackageInstallInfo installInfo = new PackageInstallInfo(pkg); - installInfo.progress = progress; - installInfo.state = PackageInstallerCompat.STATUS_INSTALLING; + int state = PackageInstallerCompat.STATUS_INSTALLING; + PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress); return new PackageInstallStateChangedTask(installInfo); }