diff options
9 files changed, 242 insertions, 48 deletions
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 5b42cad96..d7f0180fa 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 <em>removed</em> 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 <em>apps</em> contains <em>component</em>. - */ - private static boolean findActivity(ArrayList<AppInfo> 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. + * Find an AppInfo object for the given componentName + * + * @return the corresponding AppInfo or null */ - 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 1f473a26f..a5552aafd 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.<LauncherActivityInfo>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 35811d38a..707ec8642 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<AppInfo> 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<AppInfo> added = mBgAllAppsList.added; mBgAllAppsList.added = new ArrayList<AppInfo>(); + // 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 000000000..04ba1d366 --- /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 c7fe0cec8..112cca540 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<PackageInstaller.SessionInfo> getAllVerifiedSessions(); } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java index b87582f55..d7cd032f7 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<String> mActiveSessions = new SparseArray<>(); @Thunk final PackageInstaller mInstaller; private final IconCache mCache; private final Handler mWorker; + private final Context mAppContext; + private final HashMap<String,Boolean> 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<String, Integer> updateAndGetActiveSessionCache() { HashMap<String, Integer> 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<SessionInfo> getAllVerifiedSessions() { + List<SessionInfo> list = new ArrayList<>(mInstaller.getAllSessions()); + Iterator<SessionInfo> 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 5d04325e8..9c5f18917 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<AppInfo> updated = new ArrayList<>(); + final ArrayList<AppInfo> 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<ItemInfo> 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 87e987162..9e2074812 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 d6555620c..ed893c42e 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); } |