1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.notification; 18 19 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS; 20 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL; 21 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE; 22 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; 23 import static android.app.NotificationManager.INTERRUPTION_FILTER_UNKNOWN; 24 25 import android.app.ActivityManager; 26 import android.app.INotificationManager; 27 import android.app.Notification; 28 import android.app.NotificationChannel; 29 import android.app.NotificationManager; 30 import android.app.PendingIntent; 31 import android.app.Person; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.PackageManager; 36 import android.content.pm.ParceledListSlice; 37 import android.content.res.Resources; 38 import android.graphics.drawable.BitmapDrawable; 39 import android.graphics.drawable.Drawable; 40 import android.graphics.drawable.Icon; 41 import android.net.Uri; 42 import android.os.Binder; 43 import android.os.Process; 44 import android.os.RemoteException; 45 import android.os.ShellCommand; 46 import android.os.UserHandle; 47 import android.text.TextUtils; 48 import android.util.Slog; 49 50 import java.io.PrintWriter; 51 import java.net.URISyntaxException; 52 import java.util.Collections; 53 import java.util.Date; 54 55 /** 56 * Implementation of `cmd notification` in NotificationManagerService. 57 */ 58 public class NotificationShellCmd extends ShellCommand { 59 private static final String TAG = "NotifShellCmd"; 60 private static final String USAGE = "usage: cmd notification SUBCMD [args]\n\n" 61 + "SUBCMDs:\n" 62 + " allow_listener COMPONENT [user_id (current user if not specified)]\n" 63 + " disallow_listener COMPONENT [user_id (current user if not specified)]\n" 64 + " allow_assistant COMPONENT [user_id (current user if not specified)]\n" 65 + " remove_assistant COMPONENT [user_id (current user if not specified)]\n" 66 + " set_dnd [on|none (same as on)|priority|alarms|all|off (same as all)]" 67 + " allow_dnd PACKAGE [user_id (current user if not specified)]\n" 68 + " disallow_dnd PACKAGE [user_id (current user if not specified)]\n" 69 + " reset_assistant_user_set [user_id (current user if not specified)]\n" 70 + " get_approved_assistant [user_id (current user if not specified)]\n" 71 + " post [--help | flags] TAG TEXT\n" 72 + " set_bubbles PACKAGE PREFERENCE (0=none 1=all 2=selected) " 73 + "[user_id (current user if not specified)]\n" 74 + " set_bubbles_channel PACKAGE CHANNEL_ID ALLOW " 75 + "[user_id (current user if not specified)]\n" 76 + " list\n" 77 + " get <notification-key>\n" 78 + " snooze --for <msec> <notification-key>\n" 79 + " unsnooze <notification-key>\n" 80 ; 81 82 private static final String NOTIFY_USAGE = 83 "usage: cmd notification post [flags] <tag> <text>\n\n" 84 + "flags:\n" 85 + " -h|--help\n" 86 + " -v|--verbose\n" 87 + " -t|--title <text>\n" 88 + " -i|--icon <iconspec>\n" 89 + " -I|--large-icon <iconspec>\n" 90 + " -S|--style <style> [styleargs]\n" 91 + " -c|--content-intent <intentspec>\n" 92 + "\n" 93 + "styles: (default none)\n" 94 + " bigtext\n" 95 + " bigpicture --picture <iconspec>\n" 96 + " inbox --line <text> --line <text> ...\n" 97 + " messaging --conversation <title> --message <who>:<text> ...\n" 98 + " media\n" 99 + "\n" 100 + "an <iconspec> is one of\n" 101 + " file:///data/local/tmp/<img.png>\n" 102 + " content://<provider>/<path>\n" 103 + " @[<package>:]drawable/<img>\n" 104 + " data:base64,<B64DATA==>\n" 105 + "\n" 106 + "an <intentspec> is (broadcast|service|activity) <args>\n" 107 + " <args> are as described in `am start`"; 108 109 public static final int NOTIFICATION_ID = 2020; 110 public static final String CHANNEL_ID = "shell_cmd"; 111 public static final String CHANNEL_NAME = "Shell command"; 112 public static final int CHANNEL_IMP = NotificationManager.IMPORTANCE_DEFAULT; 113 114 private final NotificationManagerService mDirectService; 115 private final INotificationManager mBinderService; 116 private final PackageManager mPm; 117 private NotificationChannel mChannel; 118 NotificationShellCmd(NotificationManagerService service)119 public NotificationShellCmd(NotificationManagerService service) { 120 mDirectService = service; 121 mBinderService = service.getBinderService(); 122 mPm = mDirectService.getContext().getPackageManager(); 123 } 124 checkShellCommandPermission(int callingUid)125 protected boolean checkShellCommandPermission(int callingUid) { 126 return (callingUid == Process.ROOT_UID || callingUid == Process.SHELL_UID); 127 } 128 129 @Override onCommand(String cmd)130 public int onCommand(String cmd) { 131 if (cmd == null) { 132 return handleDefaultCommands(cmd); 133 } 134 String callingPackage = null; 135 final int callingUid = Binder.getCallingUid(); 136 final long identity = Binder.clearCallingIdentity(); 137 try { 138 if (callingUid == Process.ROOT_UID) { 139 callingPackage = NotificationManagerService.ROOT_PKG; 140 } else { 141 String[] packages = mPm.getPackagesForUid(callingUid); 142 if (packages != null && packages.length > 0) { 143 callingPackage = packages[0]; 144 } 145 } 146 } catch (Exception e) { 147 Slog.e(TAG, "failed to get caller pkg", e); 148 } finally { 149 Binder.restoreCallingIdentity(identity); 150 } 151 152 final PrintWriter pw = getOutPrintWriter(); 153 154 if (!checkShellCommandPermission(callingUid)) { 155 Slog.e(TAG, "error: permission denied: callingUid=" 156 + callingUid + " callingPackage=" + callingPackage); 157 pw.println("error: permission denied: callingUid=" 158 + callingUid + " callingPackage=" + callingPackage); 159 return 255; 160 } 161 162 try { 163 switch (cmd.replace('-', '_')) { 164 case "set_dnd": { 165 String mode = getNextArgRequired(); 166 int interruptionFilter = INTERRUPTION_FILTER_UNKNOWN; 167 switch(mode) { 168 case "none": 169 case "on": 170 interruptionFilter = INTERRUPTION_FILTER_NONE; 171 break; 172 case "priority": 173 interruptionFilter = INTERRUPTION_FILTER_PRIORITY; 174 break; 175 case "alarms": 176 interruptionFilter = INTERRUPTION_FILTER_ALARMS; 177 break; 178 case "all": 179 case "off": 180 interruptionFilter = INTERRUPTION_FILTER_ALL; 181 } 182 final int filter = interruptionFilter; 183 mBinderService.setInterruptionFilter(callingPackage, filter); 184 } 185 break; 186 case "allow_dnd": { 187 String packageName = getNextArgRequired(); 188 int userId = ActivityManager.getCurrentUser(); 189 if (peekNextArg() != null) { 190 userId = Integer.parseInt(getNextArgRequired()); 191 } 192 mBinderService.setNotificationPolicyAccessGrantedForUser( 193 packageName, userId, true); 194 } 195 break; 196 197 case "disallow_dnd": { 198 String packageName = getNextArgRequired(); 199 int userId = ActivityManager.getCurrentUser(); 200 if (peekNextArg() != null) { 201 userId = Integer.parseInt(getNextArgRequired()); 202 } 203 mBinderService.setNotificationPolicyAccessGrantedForUser( 204 packageName, userId, false); 205 } 206 break; 207 case "allow_listener": { 208 ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired()); 209 if (cn == null) { 210 pw.println("Invalid listener - must be a ComponentName"); 211 return -1; 212 } 213 int userId = ActivityManager.getCurrentUser(); 214 if (peekNextArg() != null) { 215 userId = Integer.parseInt(getNextArgRequired()); 216 } 217 mBinderService.setNotificationListenerAccessGrantedForUser( 218 cn, userId, true, true); 219 } 220 break; 221 case "disallow_listener": { 222 ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired()); 223 if (cn == null) { 224 pw.println("Invalid listener - must be a ComponentName"); 225 return -1; 226 } 227 int userId = ActivityManager.getCurrentUser(); 228 if (peekNextArg() != null) { 229 userId = Integer.parseInt(getNextArgRequired()); 230 } 231 mBinderService.setNotificationListenerAccessGrantedForUser( 232 cn, userId, false, true); 233 } 234 break; 235 case "allow_assistant": { 236 ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired()); 237 if (cn == null) { 238 pw.println("Invalid assistant - must be a ComponentName"); 239 return -1; 240 } 241 int userId = ActivityManager.getCurrentUser(); 242 if (peekNextArg() != null) { 243 userId = Integer.parseInt(getNextArgRequired()); 244 } 245 mBinderService.setNotificationAssistantAccessGrantedForUser(cn, userId, true); 246 } 247 break; 248 case "disallow_assistant": { 249 ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired()); 250 if (cn == null) { 251 pw.println("Invalid assistant - must be a ComponentName"); 252 return -1; 253 } 254 int userId = ActivityManager.getCurrentUser(); 255 if (peekNextArg() != null) { 256 userId = Integer.parseInt(getNextArgRequired()); 257 } 258 mBinderService.setNotificationAssistantAccessGrantedForUser(cn, userId, false); 259 } 260 break; 261 case "reset_assistant_user_set": { 262 int userId = ActivityManager.getCurrentUser(); 263 if (peekNextArg() != null) { 264 userId = Integer.parseInt(getNextArgRequired()); 265 } 266 mDirectService.resetAssistantUserSet(userId); 267 break; 268 } 269 case "get_approved_assistant": { 270 int userId = ActivityManager.getCurrentUser(); 271 if (peekNextArg() != null) { 272 userId = Integer.parseInt(getNextArgRequired()); 273 } 274 ComponentName approvedAssistant = mDirectService.getApprovedAssistant(userId); 275 if (approvedAssistant == null) { 276 pw.println("null"); 277 } else { 278 pw.println(approvedAssistant.flattenToString()); 279 } 280 break; 281 } 282 case "set_bubbles": { 283 // only use for testing 284 String packageName = getNextArgRequired(); 285 int preference = Integer.parseInt(getNextArgRequired()); 286 if (preference > 3 || preference < 0) { 287 pw.println("Invalid preference - must be between 0-3 " 288 + "(0=none 1=all 2=selected)"); 289 return -1; 290 } 291 int userId = ActivityManager.getCurrentUser(); 292 if (peekNextArg() != null) { 293 userId = Integer.parseInt(getNextArgRequired()); 294 } 295 int appUid = UserHandle.getUid(userId, mPm.getPackageUid(packageName, 0)); 296 mBinderService.setBubblesAllowed(packageName, appUid, preference); 297 break; 298 } 299 case "set_bubbles_channel": { 300 // only use for testing 301 String packageName = getNextArgRequired(); 302 String channelId = getNextArgRequired(); 303 boolean allow = Boolean.parseBoolean(getNextArgRequired()); 304 int userId = ActivityManager.getCurrentUser(); 305 if (peekNextArg() != null) { 306 userId = Integer.parseInt(getNextArgRequired()); 307 } 308 NotificationChannel channel = mBinderService.getNotificationChannel( 309 callingPackage, userId, packageName, channelId); 310 channel.setAllowBubbles(allow); 311 int appUid = UserHandle.getUid(userId, mPm.getPackageUid(packageName, 0)); 312 mBinderService.updateNotificationChannelForPackage(packageName, appUid, 313 channel); 314 break; 315 } 316 case "post": 317 case "notify": 318 doNotify(pw, callingPackage, callingUid); 319 break; 320 case "list": 321 for (String key : mDirectService.mNotificationsByKey.keySet()) { 322 pw.println(key); 323 } 324 break; 325 case "get": { 326 final String key = getNextArgRequired(); 327 final NotificationRecord nr = mDirectService.getNotificationRecord(key); 328 if (nr != null) { 329 nr.dump(pw, "", mDirectService.getContext(), false); 330 } else { 331 pw.println("error: no active notification matching key: " + key); 332 return 1; 333 } 334 break; 335 } 336 case "snoozed": { 337 final StringBuilder sb = new StringBuilder(); 338 final SnoozeHelper sh = mDirectService.mSnoozeHelper; 339 for (NotificationRecord nr : sh.getSnoozed()) { 340 final String pkg = nr.getSbn().getPackageName(); 341 final String key = nr.getKey(); 342 pw.println(key + " snoozed, time=" 343 + sh.getSnoozeTimeForUnpostedNotification( 344 nr.getUserId(), pkg, key) 345 + " context=" 346 + sh.getSnoozeContextForUnpostedNotification( 347 nr.getUserId(), pkg, key)); 348 } 349 break; 350 } 351 case "unsnooze": { 352 boolean mute = false; 353 String key = getNextArgRequired(); 354 if ("--mute".equals(key)) { 355 mute = true; 356 key = getNextArgRequired(); 357 } 358 if (null != mDirectService.mSnoozeHelper.getNotification(key)) { 359 pw.println("unsnoozing: " + key); 360 mDirectService.unsnoozeNotificationInt(key, null, mute); 361 } else { 362 pw.println("error: no snoozed otification matching key: " + key); 363 return 1; 364 } 365 break; 366 } 367 case "snooze": { 368 String subflag = getNextArg(); 369 if (subflag == null) { 370 subflag = "help"; 371 } else if (subflag.startsWith("--")) { 372 subflag = subflag.substring(2); 373 } 374 String flagarg = getNextArg(); 375 String key = getNextArg(); 376 if (key == null) subflag = "help"; 377 String criterion = null; 378 long duration = 0; 379 switch (subflag) { 380 case "context": 381 case "condition": 382 case "criterion": 383 criterion = flagarg; 384 break; 385 case "until": 386 case "for": 387 case "duration": 388 duration = Long.parseLong(flagarg); 389 break; 390 default: 391 pw.println("usage: cmd notification snooze (--for <msec> | " 392 + "--context <snooze-criterion-id>) <key>"); 393 return 1; 394 } 395 if (null == mDirectService.getNotificationRecord(key)) { 396 pw.println("error: no notification matching key: " + key); 397 return 1; 398 } 399 if (duration > 0 || criterion != null) { 400 if (duration > 0) { 401 pw.println(String.format("snoozing <%s> until time: %s", key, 402 new Date(System.currentTimeMillis() + duration))); 403 } else { 404 pw.println(String.format("snoozing <%s> until criterion: %s", key, 405 criterion)); 406 } 407 mDirectService.snoozeNotificationInt(key, duration, criterion, null); 408 } else { 409 pw.println("error: invalid value for --" + subflag + ": " + flagarg); 410 return 1; 411 } 412 break; 413 } 414 default: 415 return handleDefaultCommands(cmd); 416 } 417 } catch (Exception e) { 418 pw.println("Error occurred. Check logcat for details. " + e.getMessage()); 419 Slog.e(NotificationManagerService.TAG, "Error running shell command", e); 420 } 421 return 0; 422 } 423 ensureChannel(String callingPackage, int callingUid)424 void ensureChannel(String callingPackage, int callingUid) throws RemoteException { 425 final NotificationChannel channel = 426 new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, CHANNEL_IMP); 427 mBinderService.createNotificationChannels(callingPackage, 428 new ParceledListSlice<>(Collections.singletonList(channel))); 429 Slog.v(NotificationManagerService.TAG, "created channel: " 430 + mBinderService.getNotificationChannel(callingPackage, 431 UserHandle.getUserId(callingUid), callingPackage, CHANNEL_ID)); 432 } 433 parseIcon(Resources res, String encoded)434 Icon parseIcon(Resources res, String encoded) throws IllegalArgumentException { 435 if (TextUtils.isEmpty(encoded)) return null; 436 if (encoded.startsWith("/")) { 437 encoded = "file://" + encoded; 438 } 439 if (encoded.startsWith("http:") 440 || encoded.startsWith("https:") 441 || encoded.startsWith("content:") 442 || encoded.startsWith("file:") 443 || encoded.startsWith("android.resource:")) { 444 Uri asUri = Uri.parse(encoded); 445 return Icon.createWithContentUri(asUri); 446 } else if (encoded.startsWith("@")) { 447 final int resid = res.getIdentifier(encoded.substring(1), 448 "drawable", "android"); 449 if (resid != 0) { 450 return Icon.createWithResource(res, resid); 451 } 452 } else if (encoded.startsWith("data:")) { 453 encoded = encoded.substring(encoded.indexOf(',') + 1); 454 byte[] bits = android.util.Base64.decode(encoded, android.util.Base64.DEFAULT); 455 return Icon.createWithData(bits, 0, bits.length); 456 } 457 return null; 458 } 459 doNotify(PrintWriter pw, String callingPackage, int callingUid)460 private int doNotify(PrintWriter pw, String callingPackage, int callingUid) 461 throws RemoteException, URISyntaxException { 462 final Context context = mDirectService.getContext(); 463 final Resources res = context.getResources(); 464 final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID); 465 String opt; 466 467 boolean verbose = false; 468 Notification.BigPictureStyle bigPictureStyle = null; 469 Notification.BigTextStyle bigTextStyle = null; 470 Notification.InboxStyle inboxStyle = null; 471 Notification.MediaStyle mediaStyle = null; 472 Notification.MessagingStyle messagingStyle = null; 473 474 Icon smallIcon = null; 475 while ((opt = getNextOption()) != null) { 476 boolean large = false; 477 switch (opt) { 478 case "-v": 479 case "--verbose": 480 verbose = true; 481 break; 482 case "-t": 483 case "--title": 484 case "title": 485 builder.setContentTitle(getNextArgRequired()); 486 break; 487 case "-I": 488 case "--large-icon": 489 case "--largeicon": 490 case "largeicon": 491 case "large-icon": 492 large = true; 493 // fall through 494 case "-i": 495 case "--icon": 496 case "icon": 497 final String iconSpec = getNextArgRequired(); 498 final Icon icon = parseIcon(res, iconSpec); 499 if (icon == null) { 500 pw.println("error: invalid icon: " + iconSpec); 501 return -1; 502 } 503 if (large) { 504 builder.setLargeIcon(icon); 505 large = false; 506 } else { 507 smallIcon = icon; 508 } 509 break; 510 case "-c": 511 case "--content-intent": 512 case "content-intent": 513 case "--intent": 514 case "intent": 515 String intentKind = null; 516 switch (peekNextArg()) { 517 case "broadcast": 518 case "service": 519 case "activity": 520 intentKind = getNextArg(); 521 } 522 final Intent intent = Intent.parseCommandArgs(this, null); 523 if (intent.getData() == null) { 524 // force unique intents unless you know what you're doing 525 intent.setData(Uri.parse("xyz:" + System.currentTimeMillis())); 526 } 527 final PendingIntent pi; 528 if ("broadcast".equals(intentKind)) { 529 pi = PendingIntent.getBroadcastAsUser( 530 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, 531 UserHandle.CURRENT); 532 } else if ("service".equals(intentKind)) { 533 pi = PendingIntent.getService( 534 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 535 } else { 536 pi = PendingIntent.getActivityAsUser( 537 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, null, 538 UserHandle.CURRENT); 539 } 540 builder.setContentIntent(pi); 541 break; 542 case "-S": 543 case "--style": 544 final String styleSpec = getNextArgRequired().toLowerCase(); 545 switch (styleSpec) { 546 case "bigtext": 547 bigTextStyle = new Notification.BigTextStyle(); 548 builder.setStyle(bigTextStyle); 549 break; 550 case "bigpicture": 551 bigPictureStyle = new Notification.BigPictureStyle(); 552 builder.setStyle(bigPictureStyle); 553 break; 554 case "inbox": 555 inboxStyle = new Notification.InboxStyle(); 556 builder.setStyle(inboxStyle); 557 break; 558 case "messaging": 559 String name = "You"; 560 if ("--user".equals(peekNextArg())) { 561 getNextArg(); 562 name = getNextArgRequired(); 563 } 564 messagingStyle = new Notification.MessagingStyle( 565 new Person.Builder().setName(name).build()); 566 builder.setStyle(messagingStyle); 567 break; 568 case "media": 569 mediaStyle = new Notification.MediaStyle(); 570 builder.setStyle(mediaStyle); 571 break; 572 default: 573 throw new IllegalArgumentException( 574 "unrecognized notification style: " + styleSpec); 575 } 576 break; 577 case "--bigText": case "--bigtext": case "--big-text": 578 if (bigTextStyle == null) { 579 throw new IllegalArgumentException("--bigtext requires --style bigtext"); 580 } 581 bigTextStyle.bigText(getNextArgRequired()); 582 break; 583 case "--picture": 584 if (bigPictureStyle == null) { 585 throw new IllegalArgumentException("--picture requires --style bigpicture"); 586 } 587 final String pictureSpec = getNextArgRequired(); 588 final Icon pictureAsIcon = parseIcon(res, pictureSpec); 589 if (pictureAsIcon == null) { 590 throw new IllegalArgumentException("bad picture spec: " + pictureSpec); 591 } 592 final Drawable d = pictureAsIcon.loadDrawable(context); 593 if (d instanceof BitmapDrawable) { 594 bigPictureStyle.bigPicture(((BitmapDrawable) d).getBitmap()); 595 } else { 596 throw new IllegalArgumentException("not a bitmap: " + pictureSpec); 597 } 598 break; 599 case "--line": 600 if (inboxStyle == null) { 601 throw new IllegalArgumentException("--line requires --style inbox"); 602 } 603 inboxStyle.addLine(getNextArgRequired()); 604 break; 605 case "--message": 606 if (messagingStyle == null) { 607 throw new IllegalArgumentException( 608 "--message requires --style messaging"); 609 } 610 String arg = getNextArgRequired(); 611 String[] parts = arg.split(":", 2); 612 if (parts.length > 1) { 613 messagingStyle.addMessage(parts[1], System.currentTimeMillis(), 614 parts[0]); 615 } else { 616 messagingStyle.addMessage(parts[0], System.currentTimeMillis(), 617 new String[]{ 618 messagingStyle.getUserDisplayName().toString(), 619 "Them" 620 }[messagingStyle.getMessages().size() % 2]); 621 } 622 break; 623 case "--conversation": 624 if (messagingStyle == null) { 625 throw new IllegalArgumentException( 626 "--conversation requires --style messaging"); 627 } 628 messagingStyle.setConversationTitle(getNextArgRequired()); 629 break; 630 case "-h": 631 case "--help": 632 case "--wtf": 633 default: 634 pw.println(NOTIFY_USAGE); 635 return 0; 636 } 637 } 638 639 final String tag = getNextArg(); 640 final String text = getNextArg(); 641 if (tag == null || text == null) { 642 pw.println(NOTIFY_USAGE); 643 return -1; 644 } 645 646 builder.setContentText(text); 647 648 if (smallIcon == null) { 649 // uh oh, let's substitute something 650 builder.setSmallIcon(com.android.internal.R.drawable.stat_notify_chat); 651 } else { 652 builder.setSmallIcon(smallIcon); 653 } 654 655 ensureChannel(callingPackage, callingUid); 656 657 final Notification n = builder.build(); 658 pw.println("posting:\n " + n); 659 Slog.v("NotificationManager", "posting: " + n); 660 661 mBinderService.enqueueNotificationWithTag(callingPackage, callingPackage, tag, 662 NOTIFICATION_ID, n, UserHandle.getUserId(callingUid)); 663 664 if (verbose) { 665 NotificationRecord nr = mDirectService.findNotificationLocked( 666 callingPackage, tag, NOTIFICATION_ID, UserHandle.getUserId(callingUid)); 667 for (int tries = 3; tries-- > 0; ) { 668 if (nr != null) break; 669 try { 670 pw.println("waiting for notification to post..."); 671 Thread.sleep(500); 672 } catch (InterruptedException e) { 673 } 674 nr = mDirectService.findNotificationLocked( 675 callingPackage, tag, NOTIFICATION_ID, UserHandle.getUserId(callingUid)); 676 } 677 if (nr == null) { 678 pw.println("warning: couldn't find notification after enqueueing"); 679 } else { 680 pw.println("posted: "); 681 nr.dump(pw, " ", context, false); 682 } 683 } 684 685 return 0; 686 } 687 688 @Override onHelp()689 public void onHelp() { 690 getOutPrintWriter().println(USAGE); 691 } 692 } 693 694