Fix notification shell commands

- Notification posting is done as calling app
- make some only callable by shell

Test: atest
Test: sample app; make sure posted notification is from app itself
Test: sample app; make sure some commands fail
Test: make sure all modified commands still work from shell
Fixes: 143339775
Change-Id: I50ee768e792266ad2091f1913168e89d5d1463ed
(cherry picked from commit 1c943a2670)
This commit is contained in:
Julia Reynolds
2019-11-07 11:37:18 -05:00
parent 51a159329f
commit adc39de3a1
3 changed files with 70 additions and 43 deletions

View File

@@ -8638,6 +8638,7 @@ public class NotificationManagerService extends SystemService {
@VisibleForTesting @VisibleForTesting
void resetAssistantUserSet(int userId) { void resetAssistantUserSet(int userId) {
checkCallerIsSystemOrShell();
mAssistants.setUserSet(userId, false); mAssistants.setUserSet(userId, false);
handleSavePolicyFile(); handleSavePolicyFile();
} }
@@ -8645,12 +8646,14 @@ public class NotificationManagerService extends SystemService {
@VisibleForTesting @VisibleForTesting
@Nullable @Nullable
ComponentName getApprovedAssistant(int userId) { ComponentName getApprovedAssistant(int userId) {
checkCallerIsSystemOrShell();
List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
return CollectionUtils.firstOrNull(allowedComponents); return CollectionUtils.firstOrNull(allowedComponents);
} }
@VisibleForTesting @VisibleForTesting
protected void simulatePackageSuspendBroadcast(boolean suspend, String pkg) { protected void simulatePackageSuspendBroadcast(boolean suspend, String pkg) {
checkCallerIsSystemOrShell();
// only use for testing: mimic receive broadcast that package is (un)suspended // only use for testing: mimic receive broadcast that package is (un)suspended
// but does not actually (un)suspend the package // but does not actually (un)suspend the package
final Bundle extras = new Bundle(); final Bundle extras = new Bundle();
@@ -8667,6 +8670,7 @@ public class NotificationManagerService extends SystemService {
@VisibleForTesting @VisibleForTesting
protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) { protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) {
checkCallerIsSystemOrShell();
// only use for testing: mimic receive broadcast that package is (un)distracting // only use for testing: mimic receive broadcast that package is (un)distracting
// but does not actually register that info with packagemanager // but does not actually register that info with packagemanager
final Bundle extras = new Bundle(); final Bundle extras = new Bundle();

View File

@@ -16,6 +16,12 @@
package com.android.server.notification; package com.android.server.notification;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.INotificationManager; import android.app.INotificationManager;
import android.app.Notification; import android.app.Notification;
@@ -26,6 +32,7 @@ import android.app.Person;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
@@ -47,8 +54,8 @@ import java.util.Collections;
* Implementation of `cmd notification` in NotificationManagerService. * Implementation of `cmd notification` in NotificationManagerService.
*/ */
public class NotificationShellCmd extends ShellCommand { public class NotificationShellCmd extends ShellCommand {
private static final String USAGE = private static final String TAG = "NotifShellCmd";
"usage: cmd notification SUBCMD [args]\n\n" private static final String USAGE = "usage: cmd notification SUBCMD [args]\n\n"
+ "SUBCMDs:\n" + "SUBCMDs:\n"
+ " allow_listener COMPONENT [user_id (current user if not specified)]\n" + " allow_listener COMPONENT [user_id (current user if not specified)]\n"
+ " disallow_listener COMPONENT [user_id (current user if not specified)]\n" + " disallow_listener COMPONENT [user_id (current user if not specified)]\n"
@@ -89,18 +96,19 @@ public class NotificationShellCmd extends ShellCommand {
+ "an <intentspec> is (broadcast|service|activity) <args>\n" + "an <intentspec> is (broadcast|service|activity) <args>\n"
+ " <args> are as described in `am start`"; + " <args> are as described in `am start`";
public static final int NOTIFICATION_ID = 1138; public static final int NOTIFICATION_ID = 2020;
public static final String NOTIFICATION_PACKAGE = "com.android.shell"; public static final String CHANNEL_ID = "shell_cmd";
public static final String CHANNEL_ID = "shellcmd";
public static final String CHANNEL_NAME = "Shell command"; public static final String CHANNEL_NAME = "Shell command";
public static final int CHANNEL_IMP = NotificationManager.IMPORTANCE_DEFAULT; public static final int CHANNEL_IMP = NotificationManager.IMPORTANCE_DEFAULT;
private final NotificationManagerService mDirectService; private final NotificationManagerService mDirectService;
private final INotificationManager mBinderService; private final INotificationManager mBinderService;
private final PackageManager mPm;
public NotificationShellCmd(NotificationManagerService service) { public NotificationShellCmd(NotificationManagerService service) {
mDirectService = service; mDirectService = service;
mBinderService = service.getBinderService(); mBinderService = service.getBinderService();
mPm = mDirectService.getContext().getPackageManager();
} }
@Override @Override
@@ -108,9 +116,44 @@ public class NotificationShellCmd extends ShellCommand {
if (cmd == null) { if (cmd == null) {
return handleDefaultCommands(cmd); return handleDefaultCommands(cmd);
} }
String callingPackage = null;
final int callingUid = Binder.getCallingUid();
long identity = Binder.clearCallingIdentity();
try {
String[] packages = mPm.getPackagesForUid(callingUid);
if (packages != null && packages.length > 0) {
callingPackage = packages[0];
}
} catch (Exception e) {
Slog.e(TAG, "failed to get caller pkg", e);
} finally {
Binder.restoreCallingIdentity(identity);
}
final PrintWriter pw = getOutPrintWriter(); final PrintWriter pw = getOutPrintWriter();
try { try {
switch (cmd.replace('-', '_')) { switch (cmd.replace('-', '_')) {
case "set_dnd": {
String mode = getNextArgRequired();
int interruptionFilter = INTERRUPTION_FILTER_UNKNOWN;
switch(mode) {
case "none":
case "on":
interruptionFilter = INTERRUPTION_FILTER_NONE;
break;
case "priority":
interruptionFilter = INTERRUPTION_FILTER_PRIORITY;
break;
case "alarms":
interruptionFilter = INTERRUPTION_FILTER_ALARMS;
break;
case "all":
case "off":
interruptionFilter = INTERRUPTION_FILTER_ALL;
}
final int filter = interruptionFilter;
mBinderService.setInterruptionFilter(callingPackage, filter);
}
break;
case "allow_dnd": { case "allow_dnd": {
String packageName = getNextArgRequired(); String packageName = getNextArgRequired();
int userId = ActivityManager.getCurrentUser(); int userId = ActivityManager.getCurrentUser();
@@ -226,7 +269,7 @@ public class NotificationShellCmd extends ShellCommand {
} }
case "post": case "post":
case "notify": case "notify":
doNotify(pw); doNotify(pw, callingPackage, callingUid);
break; break;
default: default:
return handleDefaultCommands(cmd); return handleDefaultCommands(cmd);
@@ -238,27 +281,14 @@ public class NotificationShellCmd extends ShellCommand {
return 0; return 0;
} }
void ensureChannel() throws RemoteException { void ensureChannel(String callingPackage, int callingUid) throws RemoteException {
final int uid = Binder.getCallingUid(); final NotificationChannel channel =
final int userid = UserHandle.getCallingUserId(); new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, CHANNEL_IMP);
final long token = Binder.clearCallingIdentity(); mBinderService.createNotificationChannels(callingPackage,
try { new ParceledListSlice<>(Collections.singletonList(channel)));
if (mBinderService.getNotificationChannelForPackage(NOTIFICATION_PACKAGE,
uid, CHANNEL_ID, false) == null) {
final NotificationChannel chan = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
CHANNEL_IMP);
Slog.v(NotificationManagerService.TAG,
"creating shell channel for user " + userid + " uid " + uid + ": " + chan);
mBinderService.createNotificationChannelsForPackage(NOTIFICATION_PACKAGE, uid,
new ParceledListSlice<NotificationChannel>(
Collections.singletonList(chan)));
Slog.v(NotificationManagerService.TAG, "created channel: " Slog.v(NotificationManagerService.TAG, "created channel: "
+ mBinderService.getNotificationChannelForPackage(NOTIFICATION_PACKAGE, + mBinderService.getNotificationChannel(callingPackage,
uid, CHANNEL_ID, false)); UserHandle.getUserId(callingUid), callingPackage, CHANNEL_ID));
}
} finally {
Binder.restoreCallingIdentity(token);
}
} }
Icon parseIcon(Resources res, String encoded) throws IllegalArgumentException { Icon parseIcon(Resources res, String encoded) throws IllegalArgumentException {
@@ -287,7 +317,8 @@ public class NotificationShellCmd extends ShellCommand {
return null; return null;
} }
private int doNotify(PrintWriter pw) throws RemoteException, URISyntaxException { private int doNotify(PrintWriter pw, String callingPackage, int callingUid)
throws RemoteException, URISyntaxException {
final Context context = mDirectService.getContext(); final Context context = mDirectService.getContext();
final Resources res = context.getResources(); final Resources res = context.getResources();
final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID); final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID);
@@ -481,26 +512,18 @@ public class NotificationShellCmd extends ShellCommand {
builder.setSmallIcon(smallIcon); builder.setSmallIcon(smallIcon);
} }
ensureChannel(); ensureChannel(callingPackage, callingUid);
final Notification n = builder.build(); final Notification n = builder.build();
pw.println("posting:\n " + n); pw.println("posting:\n " + n);
Slog.v("NotificationManager", "posting: " + n); Slog.v("NotificationManager", "posting: " + n);
final int userId = UserHandle.getCallingUserId(); mBinderService.enqueueNotificationWithTag(callingPackage, callingPackage, tag,
final long token = Binder.clearCallingIdentity(); NOTIFICATION_ID, n, UserHandle.getUserId(callingUid));
try {
mBinderService.enqueueNotificationWithTag(
NOTIFICATION_PACKAGE, "android",
tag, NOTIFICATION_ID,
n, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
if (verbose) { if (verbose) {
NotificationRecord nr = mDirectService.findNotificationLocked( NotificationRecord nr = mDirectService.findNotificationLocked(
NOTIFICATION_PACKAGE, tag, NOTIFICATION_ID, userId); callingPackage, tag, NOTIFICATION_ID, UserHandle.getUserId(callingUid));
for (int tries = 3; tries-- > 0; ) { for (int tries = 3; tries-- > 0; ) {
if (nr != null) break; if (nr != null) break;
try { try {
@@ -509,7 +532,7 @@ public class NotificationShellCmd extends ShellCommand {
} catch (InterruptedException e) { } catch (InterruptedException e) {
} }
nr = mDirectService.findNotificationLocked( nr = mDirectService.findNotificationLocked(
NOTIFICATION_PACKAGE, tag, NOTIFICATION_ID, userId); callingPackage, tag, NOTIFICATION_ID, UserHandle.getUserId(callingUid));
} }
if (nr == null) { if (nr == null) {
pw.println("warning: couldn't find notification after enqueueing"); pw.println("warning: couldn't find notification after enqueueing");

View File

@@ -116,8 +116,8 @@ public class NotificationShellCmdTest extends UiServiceTestCase {
ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor<Notification> notificationCaptor =
ArgumentCaptor.forClass(Notification.class); ArgumentCaptor.forClass(Notification.class);
verify(mMockBinderService).enqueueNotificationWithTag( verify(mMockBinderService).enqueueNotificationWithTag(
eq(NotificationShellCmd.NOTIFICATION_PACKAGE), eq(getContext().getPackageName()),
eq("android"), eq(getContext().getPackageName()),
eq(aTag), eq(aTag),
eq(NotificationShellCmd.NOTIFICATION_ID), eq(NotificationShellCmd.NOTIFICATION_ID),
notificationCaptor.capture(), notificationCaptor.capture(),