diff options
author | Jahdiel Alvarez <jahdiel@google.com> | 2021-10-28 20:03:22 +0000 |
---|---|---|
committer | Jahdiel Alvarez <jahdiel@google.com> | 2021-11-19 02:30:43 +0000 |
commit | f7bd853cc001c2b235ba26c50dddd28dd2bb1e08 (patch) | |
tree | 927ebe1c563ce8b00fc318b1993d873b1434c204 /service-builtin/src | |
parent | 1ece496d66c57930e6f191eec9f8d16aa5852832 (diff) | |
download | Car-f7bd853cc001c2b235ba26c50dddd28dd2bb1e08.tar.gz |
Show CarWatchdog's resource overuse user notifications with reflection.
- Send CarWatchdog's resource overuse notification on recurring I/O
overuse only when the current user restriction doesn't require
distraction optimization.
- The notifications are sent from CarServiceBuiltin with reflection call
via NotificationHelper. To facilitate this, pass
CarServiceBuiltinPackageContext to CarWatchdogService.
- Pending intent actions from user notification buttons are handle directly:
- When the user clicks the `Prioritize app` or `Uninstall app`
notification buttons, launch app settings.
- When the user clicks the `Disable app` notification button, the intent is
redirected to CarWatchdogService, which disables the user package on
receiving the intent broadcast.
- The user notification is not canceled when the notification buttons are clicked.
This will be implemented in a separate CL.
Test: atest CarWatchdogServiceUnitTest CarWatchdogServiceTest CarServiceUnitTest:NotificationHelperTest
Bug: 197770456
Change-Id: Ib6fa80335f78e0d41a351c91455475f98139bfce
Diffstat (limited to 'service-builtin/src')
-rw-r--r-- | service-builtin/src/com/android/car/admin/NotificationHelper.java | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/service-builtin/src/com/android/car/admin/NotificationHelper.java b/service-builtin/src/com/android/car/admin/NotificationHelper.java index 2e0d40af77..cdde5c7825 100644 --- a/service-builtin/src/com/android/car/admin/NotificationHelper.java +++ b/service-builtin/src/com/android/car/admin/NotificationHelper.java @@ -16,6 +16,8 @@ package com.android.car.admin; +import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS; + import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; import android.annotation.NonNull; @@ -29,14 +31,21 @@ import android.car.builtin.util.Slogf; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; +import android.text.TextUtils; +import android.util.SparseArray; import com.android.car.R; import com.android.car.admin.ui.ManagedDeviceTextView; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; +import java.util.List; import java.util.Objects; // TODO(b/196947649): move this class to CarSettings or at least to some common package (not @@ -51,6 +60,20 @@ public final class NotificationHelper { public static final int FACTORY_RESET_NOTIFICATION_ID = 42; public static final int NEW_USER_DISCLAIMER_NOTIFICATION_ID = 108; + /* + * NOTE: IDs in the range {@code [RESOURCE_OVERUSE_NOTIFICATION_BASE_ID, + * RESOURCE_OVERUSE_NOTIFICATION_BASE_ID + RESOURCE_OVERUSE_NOTIFICATION_MAX_OFFSET)} are + * reserved for car watchdog's resource overuse notifications. + */ + /** Base notification id for car watchdog's resource overuse notifications. */ + public static final int RESOURCE_OVERUSE_NOTIFICATION_BASE_ID = 150; + + /** Maximum notification offset for car watchdog's resource overuse notifications. */ + public static final int RESOURCE_OVERUSE_NOTIFICATION_MAX_OFFSET = 20; + + public static final String ACTION_RESOURCE_OVERUSE_DISABLE_APP = + "com.android.car.watchdog.ACTION_RESOURCE_OVERUSE_DISABLE_APP"; + public static final String CAR_SERVICE_PACKAGE_NAME = "com.android.car"; @VisibleForTesting public static final String CHANNEL_ID_DEFAULT = "channel_id_default"; @VisibleForTesting @@ -158,6 +181,23 @@ public final class NotificationHelper { } /** + * Shows the user car watchdog's resource overuse notifications. + */ + public static void showResourceOveruseNotificationsAsUser(Context context, + UserHandle userHandle, List<String> headsUpNotificationPackages, + List<String> notificationCenterPackages, int idStartOffset) { + Preconditions.checkArgument(userHandle.getIdentifier() >= 0, + "Must provide the user handle for a specific user"); + + SparseArray<List<String>> packagesByImportance = new SparseArray<>(2); + packagesByImportance.put(NotificationManager.IMPORTANCE_HIGH, headsUpNotificationPackages); + packagesByImportance.put(NotificationManager.IMPORTANCE_DEFAULT, + notificationCenterPackages); + showResourceOveruseNotificationsAsUser(context, userHandle, packagesByImportance, + idStartOffset); + } + + /** * Sends the notification warning the user about the factory reset. */ public static void showFactoryResetNotification(Context context, ICarResultReceiver callback) { @@ -192,4 +232,107 @@ public final class NotificationHelper { context.getSystemService(NotificationManager.class) .notifyAsUser(TAG, FACTORY_RESET_NOTIFICATION_ID, notification, UserHandle.ALL); } + + private static void showResourceOveruseNotificationsAsUser(Context context, UserHandle user, + SparseArray<List<String>> packagesByImportance, int idStartOffset) { + PackageManager packageManager = context.getPackageManager(); + NotificationManager notificationManager = + context.getSystemService(NotificationManager.class); + + CharSequence titleTemplate = context.getText(R.string.resource_overuse_notification_title); + String textPrioritizeApp = + context.getString(R.string.resource_overuse_notification_text_prioritize_app); + String textDisableApp = + context.getString(R.string.resource_overuse_notification_text_disable_app) + " " + + textPrioritizeApp; + String textUninstallApp = + context.getString(R.string.resource_overuse_notification_text_uninstall_app) + " " + + textPrioritizeApp; + String actionTitlePrioritizeApp = + context.getString(R.string.resource_overuse_notification_button_prioritize_app); + String actionTitleDisableApp = + context.getString(R.string.resource_overuse_notification_button_disable_app); + String actionTitleUninstallApp = + context.getString(R.string.resource_overuse_notification_button_uninstall_app); + + for (int i = 0; i < packagesByImportance.size(); i++) { + int importance = packagesByImportance.keyAt(i); + List<String> packages = packagesByImportance.valueAt(i); + for (int pkgIdx = 0; pkgIdx < packages.size(); pkgIdx++) { + int idOffset = (idStartOffset + pkgIdx) % RESOURCE_OVERUSE_NOTIFICATION_MAX_OFFSET; + int notificationId = RESOURCE_OVERUSE_NOTIFICATION_BASE_ID + idOffset; + String packageName = packages.get(pkgIdx); + String text = textUninstallApp; + String negativeActionText = actionTitleUninstallApp; + + CharSequence appName; + PendingIntent positiveActionPendingIntent; + PendingIntent negativeActionPendingIntent; + try { + ApplicationInfo info = packageManager.getApplicationInfoAsUser(packageName, + /* flags= */ 0, user); + appName = info.loadLabel(packageManager); + negativeActionPendingIntent = positiveActionPendingIntent = + getAppSettingsPendingIntent(context, user, packageName, notificationId); + // Apps with SYSTEM flag are considered bundled apps by car settings and + // bundled apps have disable button rather than uninstall button. + if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + text = textDisableApp; + negativeActionText = actionTitleDisableApp; + negativeActionPendingIntent = getDisableAppPendingIntent(context, user, + packageName, notificationId); + } + } catch (PackageManager.NameNotFoundException e) { + Slogf.e(TAG, e, "Package '%s' not found for user %s", packageName, user); + continue; + } + Notification notification = NotificationHelper + .newNotificationBuilder(context, importance) + .setSmallIcon(R.drawable.car_ic_warning) + .setContentTitle(TextUtils.expandTemplate(titleTemplate, appName)) + .setContentText(text) + .setCategory(Notification.CATEGORY_CAR_WARNING) + .addAction(new Notification.Action.Builder(/* icon= */ null, + actionTitlePrioritizeApp, positiveActionPendingIntent).build()) + .addAction(new Notification.Action.Builder(/* icon= */ null, + negativeActionText, negativeActionPendingIntent).build()) + .build(); + + notificationManager.notifyAsUser(TAG, notificationId, notification, user); + + if (DEBUG) { + Slogf.d(TAG, + "Sent user notification (id %d) for resource overuse for " + + "user %s.\nNotification { App name: %s, Importance: %d, " + + "Description: %s, Positive button text: %s, Negative button " + + "text: %s }", + notificationId, user, appName, importance, text, + actionTitlePrioritizeApp, negativeActionText); + } + } + idStartOffset += packages.size(); + } + } + + // TODO(b/205900458): Send the intent to CarWatchdogService, where once the notification is + // received it should be dismissed. Pass the notification id to the intent as an extra. + private static PendingIntent getAppSettingsPendingIntent(Context context, UserHandle user, + String packageName, int notificationId) { + Intent intent = new Intent(ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.parse("package:" + packageName)); + return PendingIntent.getActivityAsUser(context, notificationId, intent, + PendingIntent.FLAG_IMMUTABLE, /* options= */ null, user); + } + + // TODO(b/205900458): Pass the notification id to the intent as an extra. + private static PendingIntent getDisableAppPendingIntent(Context context, UserHandle user, + String packageName, int notificationId) { + Intent intent = new Intent(ACTION_RESOURCE_OVERUSE_DISABLE_APP) + .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) + .putExtra(Intent.EXTRA_USER, user) + .setPackage(context.getPackageName()) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + return PendingIntent.getBroadcastAsUser(context, notificationId, intent, + PendingIntent.FLAG_IMMUTABLE, user); + } } |