aboutsummaryrefslogtreecommitdiff
path: root/service-builtin/src
diff options
context:
space:
mode:
authorJahdiel Alvarez <jahdiel@google.com>2021-10-28 20:03:22 +0000
committerJahdiel Alvarez <jahdiel@google.com>2021-11-19 02:30:43 +0000
commitf7bd853cc001c2b235ba26c50dddd28dd2bb1e08 (patch)
tree927ebe1c563ce8b00fc318b1993d873b1434c204 /service-builtin/src
parent1ece496d66c57930e6f191eec9f8d16aa5852832 (diff)
downloadCar-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.java143
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);
+ }
}