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