From 44cba696386b44f9115cad13ec9ecf67a0ac9119 Mon Sep 17 00:00:00 2001 From: Kenny Guy Date: Thu, 21 Jan 2016 19:50:02 +0000 Subject: [PATCH] Grey out suspended applications. Grey out application shortcuts and all apps entries for packages that are suspended. Bug: 22776761 Change-Id: I1b63da1816aca1de52b9f9bee62d1b162d0cdf4d --- proguard.flags | 7 +++ src/com/android/launcher3/AllAppsList.java | 19 +++++++ src/com/android/launcher3/AppInfo.java | 15 ++++- src/com/android/launcher3/BubbleTextView.java | 16 ++++-- src/com/android/launcher3/ItemInfo.java | 7 +++ src/com/android/launcher3/Launcher.java | 14 +++-- src/com/android/launcher3/LauncherModel.java | 41 ++++++++++++- src/com/android/launcher3/ShortcutInfo.java | 11 ++++ .../compat/LauncherActivityInfoCompat.java | 2 + .../launcher3/compat/LauncherAppsCompat.java | 9 ++- .../compat/LauncherAppsCompatV16.java | 4 ++ .../compat/LauncherAppsCompatVL.java | 14 ++++- .../compat/LauncherAppsCompatVN.java | 57 +++++++++++++++++++ 13 files changed, 199 insertions(+), 17 deletions(-) create mode 100644 src/com/android/launcher3/compat/LauncherAppsCompatVN.java diff --git a/proguard.flags b/proguard.flags index 05963f7bc9..19d2f0c50d 100644 --- a/proguard.flags +++ b/proguard.flags @@ -70,3 +70,10 @@ public float getBackgroundAlpha(); public void setBackgroundAlpha(float); } + +# Proguard will strip new callbacks in LauncherApps.Callback from +# WrappedCallback if compiled against an older SDK. Don't let this happen. +-keep class com.android.launcher3.compat.** { + *; +} + diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 3b25dca34c..f76c185127 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -118,6 +118,25 @@ class AllAppsList { } } + /** + * Suspend the apps for the given apk identified by packageName. + */ + public void suspendPackage(String packageName, UserHandleCompat user, boolean suspend) { + final List data = this.data; + for (int i = data.size() - 1; i >= 0; i--) { + AppInfo info = data.get(i); + final ComponentName component = info.intent.getComponent(); + if (info.user.equals(user) && packageName.equals(component.getPackageName())) { + if (suspend) { + info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } else { + info.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } + modified.add(info); + } + } + } + public void updateIconsAndLabels(HashSet packages, UserHandleCompat user, ArrayList outUpdates) { for (AppInfo info : data) { diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index 1923df76aa..32be12f01a 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -56,6 +56,11 @@ public class AppInfo extends ItemInfo { int flags = 0; + /** + * {@see ShortcutInfo#isDisabled} + */ + int isDisabled = ShortcutInfo.DEFAULT; + AppInfo() { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } @@ -75,8 +80,10 @@ public class AppInfo extends ItemInfo { IconCache iconCache) { this.componentName = info.getComponentName(); this.container = ItemInfo.NO_ID; - flags = initFlags(info); + if ((info.getApplicationInfo().flags & LauncherActivityInfoCompat.FLAG_SUSPENDED) != 0) { + isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } iconCache.getTitleAndIcon(this, info, true /* useLowResIcon */); intent = makeLaunchIntent(context, info, user); this.user = user; @@ -101,6 +108,7 @@ public class AppInfo extends ItemInfo { title = Utilities.trim(info.title); intent = new Intent(info.intent); flags = info.flags; + isDisabled = info.isDisabled; iconBitmap = info.iconBitmap; } @@ -140,4 +148,9 @@ public class AppInfo extends ItemInfo { .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) .putExtra(EXTRA_PROFILE, serialNumber); } + + @Override + public boolean isDisabled() { + return isDisabled != 0; + } } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index ce7e39252e..8842d5cdd6 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -150,7 +150,7 @@ public class BubbleTextView extends TextView Bitmap b = info.getIcon(iconCache); FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b); - if (info.isDisabled != 0) { + if (info.isDisabled()) { iconDrawable.setState(FastBitmapDrawable.State.DISABLED); } setIcon(iconDrawable, mIconSize); @@ -166,7 +166,11 @@ public class BubbleTextView extends TextView } public void applyFromApplicationInfo(AppInfo info) { - setIcon(mLauncher.createIconDrawable(info.iconBitmap), mIconSize); + FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(info.iconBitmap); + if (info.isDisabled()) { + iconDrawable.setState(FastBitmapDrawable.State.DISABLED); + } + setIcon(iconDrawable, mIconSize); setText(info.title); if (info.contentDescription != null) { setContentDescription(info.contentDescription); @@ -250,11 +254,11 @@ public class BubbleTextView extends TextView private void updateIconState() { if (mIcon instanceof FastBitmapDrawable) { FastBitmapDrawable d = (FastBitmapDrawable) mIcon; - if (isPressed() || mStayPressed) { - d.animateState(FastBitmapDrawable.State.PRESSED); - } else if (getTag() instanceof ShortcutInfo - && ((ShortcutInfo) getTag()).isDisabled != 0) { + if (getTag() instanceof ItemInfo + && ((ItemInfo) getTag()).isDisabled()) { d.animateState(FastBitmapDrawable.State.DISABLED); + } else if (isPressed() || mStayPressed) { + d.animateState(FastBitmapDrawable.State.PRESSED); } else { d.animateState(FastBitmapDrawable.State.NORMAL); } diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index aa5a18d3c4..1ba09e1eb8 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -198,4 +198,11 @@ public class ItemInfo { + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")"; } + + /** + * Whether this item is disabled. + */ + public boolean isDisabled() { + return false; + } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 0df30841d3..6a548c38db 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2652,12 +2652,16 @@ public class Launcher extends Activity final ShortcutInfo shortcut = (ShortcutInfo) tag; if (shortcut.isDisabled != 0) { - int error = R.string.activity_not_available; - if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) { - error = R.string.safemode_shortcut_error; + if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SUSPENDED) != 0) { + // Launch activity anyway, framework will tell the user why the app is suspended. + } else { + int error = R.string.activity_not_available; + if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) { + error = R.string.safemode_shortcut_error; + } + Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); + return; } - Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); - return; } // Check for abandoned promise diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 92ef3eae76..52d2dca659 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -1198,6 +1198,20 @@ public class LauncherModel extends BroadcastReceiver } } + @Override + public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { + enqueuePackageUpdated(new PackageUpdatedTask( + PackageUpdatedTask.OP_SUSPEND, packageNames, + user)); + } + + @Override + public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) { + enqueuePackageUpdated(new PackageUpdatedTask( + PackageUpdatedTask.OP_UNSUSPEND, packageNames, + user)); + } + /** * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and * ACTION_PACKAGE_CHANGED. @@ -1777,6 +1791,11 @@ public class LauncherModel extends BroadcastReceiver restoredRows.add(id); restored = false; } + boolean isSuspended = launcherApps.isPackageSuspendedForProfile( + cn.getPackageName(), user); + if (isSuspended) { + disabledState = ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } } else if (validPkg) { intent = null; if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) { @@ -2892,6 +2911,8 @@ public class LauncherModel extends BroadcastReceiver public static final int OP_UPDATE = 2; public static final int OP_REMOVE = 3; // uninstlled public static final int OP_UNAVAILABLE = 4; // external media unmounted + public static final int OP_SUSPEND = 5; // package suspended + public static final int OP_UNSUSPEND = 6; // package unsuspended public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) { @@ -2949,6 +2970,15 @@ public class LauncherModel extends BroadcastReceiver mApp.getWidgetCache().removePackage(packages[i], mUser); } break; + case OP_SUSPEND: + case OP_UNSUSPEND: + boolean suspend = mOp == OP_SUSPEND; + for (int i=0; i added = null; @@ -3001,7 +3031,7 @@ public class LauncherModel extends BroadcastReceiver } // Update shortcut infos - if (mOp == OP_ADD || mOp == OP_UPDATE) { + if (mOp == OP_ADD || mOp == OP_UPDATE || mOp == OP_UNSUSPEND) { final ArrayList updatedShortcuts = new ArrayList(); final ArrayList removedShortcuts = new ArrayList(); final ArrayList widgets = new ArrayList(); @@ -3014,6 +3044,11 @@ public class LauncherModel extends BroadcastReceiver boolean infoUpdated = false; boolean shortcutUpdated = false; + if (mOp == OP_UNSUSPEND) { + si.isDisabled &= ~ ShortcutInfo.FLAG_DISABLED_SUSPENDED; + infoUpdated = true; + } + // Update shortcuts which use iconResource. if ((si.iconResource != null) && packageSet.contains(si.iconResource.packageName)) { @@ -3139,7 +3174,7 @@ public class LauncherModel extends BroadcastReceiver final ArrayList removedPackageNames = new ArrayList(); - if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) { + if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE || mOp == OP_SUSPEND) { // Mark all packages in the broadcast to be removed removedPackageNames.addAll(Arrays.asList(packages)); } else if (mOp == OP_UPDATE) { @@ -3155,6 +3190,8 @@ public class LauncherModel extends BroadcastReceiver final int removeReason; if (mOp == OP_UNAVAILABLE) { removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE; + } else if (mOp == OP_SUSPEND) { + removeReason = ShortcutInfo.FLAG_DISABLED_SUSPENDED; } else { // Remove all the components associated with this package for (String pn : removedPackageNames) { diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index baa3cd02c1..0d6708bfc8 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -111,6 +111,11 @@ public class ShortcutInfo extends ItemInfo { */ public static final int FLAG_DISABLED_NOT_AVAILABLE = 2; + /** + * Indicates that the icon is disabled as the app is suspended + */ + public static final int FLAG_DISABLED_SUSPENDED = 4; + /** * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when * sd-card is not available). @@ -177,6 +182,7 @@ public class ShortcutInfo extends ItemInfo { intent = new Intent(info.intent); customIcon = false; flags = info.flags; + isDisabled = info.isDisabled; } public void setIcon(Bitmap b) { @@ -278,5 +284,10 @@ public class ShortcutInfo extends ItemInfo { shortcut.flags = AppInfo.initFlags(info); return shortcut; } + + @Override + public boolean isDisabled() { + return isDisabled != 0; + } } diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java index 0bc9588aae..aaf756edaf 100644 --- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java +++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java @@ -24,6 +24,8 @@ import android.graphics.drawable.Drawable; public abstract class LauncherActivityInfoCompat { + public static final int FLAG_SUSPENDED = 1<<30; + LauncherActivityInfoCompat() { } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index 95e3ba9024..da3eb8fee5 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -42,6 +42,8 @@ public abstract class LauncherAppsCompat { void onPackageChanged(String packageName, UserHandleCompat user); void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing); void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing); + void onPackagesSuspended(String[] packageNames, UserHandleCompat user); + void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user); } protected LauncherAppsCompat() { @@ -53,7 +55,9 @@ public abstract class LauncherAppsCompat { public static LauncherAppsCompat getInstance(Context context) { synchronized (sInstanceLock) { if (sInstance == null) { - if (Utilities.ATLEAST_LOLLIPOP) { + if (Utilities.isNycOrAbove()) { + sInstance = new LauncherAppsCompatVN(context.getApplicationContext()); + } else if (Utilities.ATLEAST_LOLLIPOP) { sInstance = new LauncherAppsCompatVL(context.getApplicationContext()); } else { sInstance = new LauncherAppsCompatV16(context.getApplicationContext()); @@ -75,6 +79,7 @@ public abstract class LauncherAppsCompat { public abstract boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user); public abstract boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user); + public abstract boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user); public boolean isAppEnabled(PackageManager pm, String packageName, int flags) { try { @@ -84,4 +89,4 @@ public abstract class LauncherAppsCompat { return false; } } -} \ No newline at end of file +} diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java index 339c457e18..2d0778d306 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java @@ -126,6 +126,10 @@ public class LauncherAppsCompatV16 extends LauncherAppsCompat { } } + public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) { + return false; + } + private void unregisterForPackageIntents() { mContext.unregisterReceiver(mPackageMonitor); } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java index fbf91b548c..7270d023be 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java @@ -36,7 +36,7 @@ import java.util.Map; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class LauncherAppsCompatVL extends LauncherAppsCompat { - private LauncherApps mLauncherApps; + protected LauncherApps mLauncherApps; private Map mCallbacks = new HashMap(); @@ -106,6 +106,10 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { return mLauncherApps.isActivityEnabled(component, user.getUser()); } + public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) { + return false; + } + private static class WrappedCallback extends LauncherApps.Callback { private LauncherAppsCompat.OnAppsChangedCallbackCompat mCallback; @@ -134,6 +138,14 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { mCallback.onPackagesUnavailable(packageNames, UserHandleCompat.fromUser(user), replacing); } + + public void onPackagesSuspended(String[] packageNames, UserHandle user) { + mCallback.onPackagesSuspended(packageNames, UserHandleCompat.fromUser(user)); + } + + public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { + mCallback.onPackagesUnsuspended(packageNames, UserHandleCompat.fromUser(user)); + } } } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVN.java b/src/com/android/launcher3/compat/LauncherAppsCompatVN.java new file mode 100644 index 0000000000..0d883b6fd0 --- /dev/null +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVN.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 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.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherApps; +import android.os.UserHandle; +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +//TODO: Once gogole3 SDK is updated to N, add @TargetApi(Build.VERSION_CODES.N) +public class LauncherAppsCompatVN extends LauncherAppsCompatVL { + + private static final String TAG = "LauncherAppsCompatVN"; + + LauncherAppsCompatVN(Context context) { + super(context); + } + + @Override + public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) { + if (user != null && packageName != null) { + try { + //TODO: Replace with proper API call once google3 SDK is updated. + Method getApplicationInfoMethod = LauncherApps.class.getMethod("getApplicationInfo", + String.class, int.class, UserHandle.class); + + ApplicationInfo info = (ApplicationInfo) getApplicationInfoMethod.invoke( + mLauncherApps, packageName, 0, user.getUser()); + if (info != null) { + return (info.flags & LauncherActivityInfoCompat.FLAG_SUSPENDED) != 0; + } + } catch (NoSuchMethodError | NoSuchMethodException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + Log.e(TAG, "Running on N without getApplicationInfo", e); + } + } + return false; + } +}