1 /* 2 * Copyright (C) 2012 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.settings.notification.history; 18 19 import static android.provider.Settings.EXTRA_APP_PACKAGE; 20 import static android.provider.Settings.EXTRA_CHANNEL_ID; 21 22 import android.app.Activity; 23 import android.app.ActivityManager; 24 import android.app.INotificationManager; 25 import android.app.Notification; 26 import android.app.NotificationChannel; 27 import android.app.PendingIntent; 28 import android.app.settings.SettingsEnums; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentSender; 33 import android.content.pm.ApplicationInfo; 34 import android.content.pm.PackageManager; 35 import android.graphics.PorterDuff; 36 import android.graphics.Typeface; 37 import android.graphics.drawable.Drawable; 38 import android.os.Bundle; 39 import android.os.Parcel; 40 import android.os.RemoteException; 41 import android.os.ServiceManager; 42 import android.os.UserHandle; 43 import android.provider.Settings; 44 import android.service.notification.NotificationListenerService; 45 import android.service.notification.NotificationListenerService.Ranking; 46 import android.service.notification.NotificationListenerService.RankingMap; 47 import android.service.notification.StatusBarNotification; 48 import android.text.SpannableString; 49 import android.text.SpannableStringBuilder; 50 import android.text.TextUtils; 51 import android.text.style.StyleSpan; 52 import android.util.Log; 53 import android.view.View; 54 import android.view.ViewGroup; 55 import android.widget.DateTimeView; 56 import android.widget.ImageView; 57 import android.widget.TextView; 58 59 import androidx.preference.Preference; 60 import androidx.preference.PreferenceViewHolder; 61 import androidx.recyclerview.widget.RecyclerView; 62 63 import com.android.settings.R; 64 import com.android.settings.SettingsPreferenceFragment; 65 import com.android.settings.Utils; 66 67 import java.util.ArrayList; 68 import java.util.Comparator; 69 import java.util.LinkedList; 70 import java.util.List; 71 72 public class NotificationStation extends SettingsPreferenceFragment { 73 private static final String TAG = NotificationStation.class.getSimpleName(); 74 75 private static final boolean DEBUG = false; 76 private static final boolean DUMP_EXTRAS = true; 77 private static final boolean DUMP_PARCEL = true; 78 79 private static class HistoricalNotificationInfo { 80 public String key; 81 public NotificationChannel channel; 82 // Historical notifications don't have Ranking information. for most fields that's ok 83 // but we need channel id to launch settings. 84 public String channelId; 85 public String pkg; 86 public Drawable pkgicon; 87 public CharSequence pkgname; 88 public Drawable icon; 89 public boolean badged; 90 public CharSequence title; 91 public CharSequence text; 92 public int priority; 93 public int user; 94 public long timestamp; 95 public boolean active; 96 public CharSequence notificationExtra; 97 public CharSequence rankingExtra; 98 public boolean alerted; 99 public boolean visuallyInterruptive; 100 updateFrom(HistoricalNotificationInfo updatedInfo)101 public void updateFrom(HistoricalNotificationInfo updatedInfo) { 102 this.channel = updatedInfo.channel; 103 this.icon = updatedInfo.icon; 104 this.title = updatedInfo.title; 105 this.text = updatedInfo.text; 106 this.priority = updatedInfo.priority; 107 this.timestamp = updatedInfo.timestamp; 108 this.active = updatedInfo.active; 109 this.alerted = updatedInfo.alerted; 110 this.visuallyInterruptive = updatedInfo.visuallyInterruptive; 111 this.notificationExtra = updatedInfo.notificationExtra; 112 this.rankingExtra = updatedInfo.rankingExtra; 113 } 114 } 115 116 private PackageManager mPm; 117 private INotificationManager mNoMan; 118 private RankingMap mRanking; 119 private LinkedList<HistoricalNotificationInfo> mNotificationInfos; 120 121 private final NotificationListenerService mListener = new NotificationListenerService() { 122 @Override 123 public void onNotificationPosted(StatusBarNotification sbn, RankingMap ranking) { 124 logd("onNotificationPosted: %s, with update for %d", sbn.getNotification(), 125 ranking == null ? 0 : ranking.getOrderedKeys().length); 126 mRanking = ranking; 127 if (sbn.getNotification().isGroupSummary()) { 128 return; 129 } 130 addOrUpdateNotification(sbn); 131 } 132 133 @Override 134 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap ranking) { 135 logd("onNotificationRankingUpdate with update for %d", 136 ranking == null ? 0 : ranking.getOrderedKeys().length); 137 mRanking = ranking; 138 if (sbn.getNotification().isGroupSummary()) { 139 return; 140 } 141 markNotificationAsDismissed(sbn); 142 } 143 144 @Override 145 public void onNotificationRankingUpdate(RankingMap ranking) { 146 logd("onNotificationRankingUpdate with update for %d", 147 ranking == null ? 0 : ranking.getOrderedKeys().length); 148 mRanking = ranking; 149 updateNotificationsFromRanking(); 150 } 151 152 @Override 153 public void onListenerConnected() { 154 mRanking = getCurrentRanking(); 155 logd("onListenerConnected with update for %d", 156 mRanking == null ? 0 : mRanking.getOrderedKeys().length); 157 populateNotifications(); 158 } 159 }; 160 161 private Context mContext; 162 163 private final Comparator<HistoricalNotificationInfo> mNotificationSorter 164 = (lhs, rhs) -> Long.compare(rhs.timestamp, lhs.timestamp); 165 166 @Override onAttach(Activity activity)167 public void onAttach(Activity activity) { 168 logd("onAttach(%s)", activity.getClass().getSimpleName()); 169 super.onAttach(activity); 170 mContext = activity; 171 mPm = mContext.getPackageManager(); 172 mNoMan = INotificationManager.Stub.asInterface( 173 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 174 mNotificationInfos = new LinkedList<>(); 175 } 176 177 @Override onDetach()178 public void onDetach() { 179 logd("onDetach()"); 180 super.onDetach(); 181 } 182 183 @Override onPause()184 public void onPause() { 185 try { 186 mListener.unregisterAsSystemService(); 187 } catch (RemoteException e) { 188 Log.e(TAG, "Cannot unregister listener", e); 189 } 190 super.onPause(); 191 } 192 193 @Override getMetricsCategory()194 public int getMetricsCategory() { 195 return SettingsEnums.NOTIFICATION_STATION; 196 } 197 198 @Override onActivityCreated(Bundle savedInstanceState)199 public void onActivityCreated(Bundle savedInstanceState) { 200 logd("onActivityCreated(%s)", savedInstanceState); 201 super.onActivityCreated(savedInstanceState); 202 203 RecyclerView listView = getListView(); 204 Utils.forceCustomPadding(listView, false /* non additive padding */); 205 } 206 207 @Override onResume()208 public void onResume() { 209 logd("onResume()"); 210 super.onResume(); 211 try { 212 mListener.registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(), 213 this.getClass().getCanonicalName()), ActivityManager.getCurrentUser()); 214 } catch (RemoteException e) { 215 Log.e(TAG, "Cannot register listener", e); 216 } 217 } 218 219 /** 220 * Adds all current and historical notifications when the NLS connects. 221 */ populateNotifications()222 private void populateNotifications() { 223 loadNotifications(); 224 final int N = mNotificationInfos.size(); 225 logd("adding %d infos", N); 226 if (getPreferenceScreen() == null) { 227 setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext())); 228 } 229 getPreferenceScreen().removeAll(); 230 for (int i = 0; i < N; i++) { 231 getPreferenceScreen().addPreference(new HistoricalNotificationPreference( 232 getPrefContext(), mNotificationInfos.get(i), i)); 233 } 234 } 235 236 /** 237 * Finds and dims the given notification in the preferences list. 238 */ markNotificationAsDismissed(StatusBarNotification sbn)239 private void markNotificationAsDismissed(StatusBarNotification sbn) { 240 final int N = mNotificationInfos.size(); 241 for (int i = 0; i < N; i++) { 242 final HistoricalNotificationInfo info = mNotificationInfos.get(i); 243 if (TextUtils.equals(info.key, sbn.getKey())) { 244 info.active = false; 245 ((HistoricalNotificationPreference) getPreferenceScreen().findPreference( 246 sbn.getKey())).updatePreference(info); 247 break; 248 } 249 } 250 } 251 252 /** 253 * Either updates a notification with its latest information or (if it's something the user 254 * would consider a new notification) adds a new entry at the start of the list. 255 */ addOrUpdateNotification(StatusBarNotification sbn)256 private void addOrUpdateNotification(StatusBarNotification sbn) { 257 HistoricalNotificationInfo newInfo = createFromSbn(sbn, true); 258 boolean needsAdd = true; 259 final int N = mNotificationInfos.size(); 260 for (int i = 0; i < N; i++) { 261 final HistoricalNotificationInfo info = mNotificationInfos.get(i); 262 if (TextUtils.equals(info.key, sbn.getKey()) && info.active 263 && !newInfo.alerted && !newInfo.visuallyInterruptive) { 264 info.updateFrom(newInfo); 265 266 ((HistoricalNotificationPreference) getPreferenceScreen().findPreference( 267 sbn.getKey())).updatePreference(info); 268 needsAdd = false; 269 break; 270 } 271 } 272 if (needsAdd) { 273 mNotificationInfos.addFirst(newInfo); 274 getPreferenceScreen().addPreference(new HistoricalNotificationPreference( 275 getPrefContext(), mNotificationInfos.peekFirst(), 276 -1 * mNotificationInfos.size())); 277 } 278 } 279 280 /** 281 * Updates all notifications in the list based on new information in the ranking. 282 */ updateNotificationsFromRanking()283 private void updateNotificationsFromRanking() { 284 Ranking rank = new Ranking(); 285 for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) { 286 final HistoricalNotificationPreference p = 287 (HistoricalNotificationPreference) getPreferenceScreen().getPreference(i); 288 final HistoricalNotificationInfo info = mNotificationInfos.get(i); 289 mRanking.getRanking(p.getKey(), rank); 290 291 updateFromRanking(info); 292 ((HistoricalNotificationPreference) getPreferenceScreen().findPreference( 293 info.key)).updatePreference(info); 294 } 295 } 296 logd(String msg, Object... args)297 private static void logd(String msg, Object... args) { 298 if (DEBUG) { 299 Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); 300 } 301 } 302 bold(CharSequence cs)303 private static CharSequence bold(CharSequence cs) { 304 if (cs.length() == 0) return cs; 305 SpannableString ss = new SpannableString(cs); 306 ss.setSpan(new StyleSpan(Typeface.BOLD), 0, cs.length(), 0); 307 return ss; 308 } 309 getTitleString(Notification n)310 private static String getTitleString(Notification n) { 311 CharSequence title = null; 312 if (n.extras != null) { 313 title = n.extras.getCharSequence(Notification.EXTRA_TITLE); 314 } 315 return title == null? "" : String.valueOf(title); 316 } 317 318 /** 319 * Returns the appropriate substring for this notification based on the style of notification. 320 */ getTextString(Context appContext, Notification n)321 private static String getTextString(Context appContext, Notification n) { 322 CharSequence text = null; 323 if (n.extras != null) { 324 text = n.extras.getCharSequence(Notification.EXTRA_TEXT); 325 326 Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, n); 327 328 if (nb.getStyle() instanceof Notification.BigTextStyle) { 329 text = ((Notification.BigTextStyle) nb.getStyle()).getBigText(); 330 } else if (nb.getStyle() instanceof Notification.MessagingStyle) { 331 Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle(); 332 final List<Notification.MessagingStyle.Message> messages = ms.getMessages(); 333 if (messages != null && messages.size() > 0) { 334 text = messages.get(messages.size() - 1).getText(); 335 } 336 } 337 338 if (TextUtils.isEmpty(text)) { 339 text = n.extras.getCharSequence(Notification.EXTRA_TEXT); 340 } 341 } 342 return text == null ? "" : String.valueOf(text); 343 } 344 loadIcon(HistoricalNotificationInfo info, StatusBarNotification sbn)345 private Drawable loadIcon(HistoricalNotificationInfo info, StatusBarNotification sbn) { 346 Drawable draw = sbn.getNotification().getSmallIcon().loadDrawableAsUser( 347 sbn.getPackageContext(mContext), info.user); 348 if (draw == null) { 349 return null; 350 } 351 draw.mutate(); 352 draw.setColorFilter(sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP); 353 return draw; 354 } 355 formatPendingIntent(PendingIntent pi)356 private static String formatPendingIntent(PendingIntent pi) { 357 final StringBuilder sb = new StringBuilder(); 358 final IntentSender is = pi.getIntentSender(); 359 sb.append("Intent(pkg=").append(is.getCreatorPackage()); 360 try { 361 final boolean isActivity = 362 ActivityManager.getService().isIntentSenderAnActivity(is.getTarget()); 363 if (isActivity) sb.append(" (activity)"); 364 } catch (RemoteException ex) {} 365 sb.append(")"); 366 return sb.toString(); 367 } 368 369 /** 370 * Reads all current and past notifications (up to the system limit, since the device was 371 * booted), stores the data we need to present them, and sorts them chronologically for display. 372 */ loadNotifications()373 private void loadNotifications() { 374 try { 375 StatusBarNotification[] active = mNoMan.getActiveNotificationsWithAttribution( 376 mContext.getPackageName(), mContext.getAttributionTag()); 377 StatusBarNotification[] dismissed = mNoMan.getHistoricalNotificationsWithAttribution( 378 mContext.getPackageName(), mContext.getAttributionTag(), 50, false); 379 380 List<HistoricalNotificationInfo> list 381 = new ArrayList<>(active.length + dismissed.length); 382 383 for (StatusBarNotification[] resultSet 384 : new StatusBarNotification[][] { active, dismissed }) { 385 for (StatusBarNotification sbn : resultSet) { 386 if (sbn.getNotification().isGroupSummary()) { 387 continue; 388 } 389 final HistoricalNotificationInfo info = createFromSbn(sbn, resultSet == active); 390 logd(" [%d] %s: %s", info.timestamp, info.pkg, info.title); 391 list.add(info); 392 } 393 } 394 395 // notifications are given to us in the same order as the shade; sorted by inferred 396 // priority. Resort chronologically for our display. 397 list.sort(mNotificationSorter); 398 mNotificationInfos = new LinkedList<>(list); 399 400 } catch (RemoteException e) { 401 Log.e(TAG, "Cannot load Notifications: ", e); 402 } 403 } 404 createFromSbn(StatusBarNotification sbn, boolean active)405 private HistoricalNotificationInfo createFromSbn(StatusBarNotification sbn, boolean active) { 406 final Notification n = sbn.getNotification(); 407 final HistoricalNotificationInfo info = new HistoricalNotificationInfo(); 408 info.pkg = sbn.getPackageName(); 409 info.user = sbn.getUserId() == UserHandle.USER_ALL 410 ? UserHandle.USER_SYSTEM : sbn.getUserId(); 411 info.badged = info.user != ActivityManager.getCurrentUser(); 412 info.icon = loadIcon(info, sbn); 413 if (info.icon == null) { 414 info.icon = loadPackageIconDrawable(info.pkg, info.user); 415 } 416 info.pkgname = loadPackageName(info.pkg); 417 info.title = getTitleString(n); 418 info.text = getTextString(sbn.getPackageContext(mContext), n); 419 info.timestamp = sbn.getPostTime(); 420 info.priority = n.priority; 421 info.key = sbn.getKey(); 422 info.channelId = sbn.getNotification().getChannelId(); 423 424 info.active = active; 425 info.notificationExtra = generateExtraText(sbn, info); 426 427 updateFromRanking(info); 428 429 return info; 430 } 431 updateFromRanking(HistoricalNotificationInfo info)432 private void updateFromRanking(HistoricalNotificationInfo info) { 433 Ranking rank = new Ranking(); 434 if (mRanking == null) { 435 return; 436 } 437 mRanking.getRanking(info.key, rank); 438 info.alerted = rank.getLastAudiblyAlertedMillis() > 0; 439 info.visuallyInterruptive = rank.visuallyInterruptive(); 440 info.channel = rank.getChannel(); 441 info.rankingExtra = generateRankingExtraText(info); 442 } 443 444 /** 445 * Generates a string of debug information for this notification based on the RankingMap 446 */ generateRankingExtraText(HistoricalNotificationInfo info)447 private CharSequence generateRankingExtraText(HistoricalNotificationInfo info) { 448 final SpannableStringBuilder sb = new SpannableStringBuilder(); 449 final String delim = getString(R.string.notification_log_details_delimiter); 450 451 Ranking rank = new Ranking(); 452 if (mRanking != null && mRanking.getRanking(info.key, rank)) { 453 if (info.active && info.alerted) { 454 sb.append("\n") 455 .append(bold(getString(R.string.notification_log_details_alerted))); 456 } 457 sb.append("\n") 458 .append(bold(getString(R.string.notification_log_channel))) 459 .append(delim) 460 .append(info.channel.toString()); 461 sb.append("\n") 462 .append(bold("getShortcutInfo")) 463 .append(delim) 464 .append(String.valueOf(rank.getConversationShortcutInfo())); 465 sb.append("\n") 466 .append(bold("isConversation")) 467 .append(delim) 468 .append(rank.isConversation() ? "true" : "false"); 469 sb.append("\n") 470 .append(bold("isBubble")) 471 .append(delim) 472 .append(rank.isBubble() ? "true" : "false"); 473 if (info.active) { 474 sb.append("\n") 475 .append(bold(getString( 476 R.string.notification_log_details_importance))) 477 .append(delim) 478 .append(Ranking.importanceToString(rank.getImportance())); 479 if (rank.getImportanceExplanation() != null) { 480 sb.append("\n") 481 .append(bold(getString( 482 R.string.notification_log_details_explanation))) 483 .append(delim) 484 .append(rank.getImportanceExplanation()); 485 } 486 sb.append("\n") 487 .append(bold(getString( 488 R.string.notification_log_details_badge))) 489 .append(delim) 490 .append(Boolean.toString(rank.canShowBadge())); 491 } 492 } else { 493 if (mRanking == null) { 494 sb.append("\n") 495 .append(bold(getString( 496 R.string.notification_log_details_ranking_null))); 497 } else { 498 sb.append("\n") 499 .append(bold(getString( 500 R.string.notification_log_details_ranking_none))); 501 } 502 } 503 504 return sb; 505 } 506 507 /** 508 * Generates a string of debug information for this notification 509 */ generateExtraText(StatusBarNotification sbn, HistoricalNotificationInfo info)510 private CharSequence generateExtraText(StatusBarNotification sbn, 511 HistoricalNotificationInfo info) { 512 final Notification n = sbn.getNotification(); 513 final SpannableStringBuilder sb = new SpannableStringBuilder(); 514 final String delim = getString(R.string.notification_log_details_delimiter); 515 sb.append(bold(getString(R.string.notification_log_details_package))) 516 .append(delim) 517 .append(info.pkg) 518 .append("\n") 519 .append(bold(getString(R.string.notification_log_details_key))) 520 .append(delim) 521 .append(sbn.getKey()); 522 sb.append("\n") 523 .append(bold(getString(R.string.notification_log_details_icon))) 524 .append(delim) 525 .append(String.valueOf(n.getSmallIcon())); 526 sb.append("\n") 527 .append(bold("postTime")) 528 .append(delim) 529 .append(String.valueOf(sbn.getPostTime())); 530 if (n.getTimeoutAfter() != 0) { 531 sb.append("\n") 532 .append(bold("timeoutAfter")) 533 .append(delim) 534 .append(String.valueOf(n.getTimeoutAfter())); 535 } 536 if (sbn.isGroup()) { 537 sb.append("\n") 538 .append(bold(getString(R.string.notification_log_details_group))) 539 .append(delim) 540 .append(String.valueOf(sbn.getGroupKey())); 541 if (n.isGroupSummary()) { 542 sb.append(bold( 543 getString(R.string.notification_log_details_group_summary))); 544 } 545 } 546 if (n.publicVersion != null) { 547 sb.append("\n") 548 .append(bold(getString( 549 R.string.notification_log_details_public_version))) 550 .append(delim) 551 .append(getTitleString(n.publicVersion)); 552 } 553 554 if (n.contentIntent != null) { 555 sb.append("\n") 556 .append(bold(getString( 557 R.string.notification_log_details_content_intent))) 558 .append(delim) 559 .append(formatPendingIntent(n.contentIntent)); 560 } 561 if (n.deleteIntent != null) { 562 sb.append("\n") 563 .append(bold(getString( 564 R.string.notification_log_details_delete_intent))) 565 .append(delim) 566 .append(formatPendingIntent(n.deleteIntent)); 567 } 568 if (n.fullScreenIntent != null) { 569 sb.append("\n") 570 .append(bold(getString( 571 R.string.notification_log_details_full_screen_intent))) 572 .append(delim) 573 .append(formatPendingIntent(n.fullScreenIntent)); 574 } 575 if (n.actions != null && n.actions.length > 0) { 576 sb.append("\n") 577 .append(bold(getString(R.string.notification_log_details_actions))); 578 for (int ai=0; ai<n.actions.length; ai++) { 579 final Notification.Action action = n.actions[ai]; 580 sb.append("\n ").append(String.valueOf(ai)).append(' ') 581 .append(bold(getString( 582 R.string.notification_log_details_title))) 583 .append(delim) 584 .append(action.title); 585 if (action.actionIntent != null) { 586 sb.append("\n ") 587 .append(bold(getString( 588 R.string.notification_log_details_content_intent))) 589 .append(delim) 590 .append(formatPendingIntent(action.actionIntent)); 591 } 592 if (action.getRemoteInputs() != null) { 593 sb.append("\n ") 594 .append(bold(getString( 595 R.string.notification_log_details_remoteinput))) 596 .append(delim) 597 .append(String.valueOf(action.getRemoteInputs().length)); 598 } 599 } 600 } 601 if (n.contentView != null) { 602 sb.append("\n") 603 .append(bold(getString( 604 R.string.notification_log_details_content_view))) 605 .append(delim) 606 .append(n.contentView.toString()); 607 } 608 if (n.getBubbleMetadata() != null) { 609 sb.append("\n") 610 .append(bold("bubbleMetadata")) 611 .append(delim) 612 .append(String.valueOf(n.getBubbleMetadata())); 613 } 614 if (n.getShortcutId() != null) { 615 sb.append("\n") 616 .append(bold("shortcutId")) 617 .append(delim) 618 .append(String.valueOf(n.getShortcutId())); 619 } 620 621 if (DUMP_EXTRAS) { 622 if (n.extras != null && n.extras.size() > 0) { 623 sb.append("\n") 624 .append(bold(getString( 625 R.string.notification_log_details_extras))); 626 for (String extraKey : n.extras.keySet()) { 627 String val = String.valueOf(n.extras.get(extraKey)); 628 if (val.length() > 100) val = val.substring(0, 100) + "..."; 629 sb.append("\n ").append(extraKey).append(delim).append(val); 630 } 631 } 632 } 633 if (DUMP_PARCEL) { 634 final Parcel p = Parcel.obtain(); 635 n.writeToParcel(p, 0); 636 sb.append("\n") 637 .append(bold(getString(R.string.notification_log_details_parcel))) 638 .append(delim) 639 .append(String.valueOf(p.dataPosition())) 640 .append(' ') 641 .append(bold(getString(R.string.notification_log_details_ashmem))) 642 .append(delim) 643 .append(String.valueOf(p.getBlobAshmemSize())) 644 .append("\n"); 645 } 646 return sb; 647 } 648 loadPackageIconDrawable(String pkg, int userId)649 private Drawable loadPackageIconDrawable(String pkg, int userId) { 650 Drawable icon = null; 651 try { 652 icon = mPm.getApplicationIcon(pkg); 653 } catch (PackageManager.NameNotFoundException e) { 654 Log.e(TAG, "Cannot get application icon", e); 655 } 656 657 return icon; 658 } 659 loadPackageName(String pkg)660 private CharSequence loadPackageName(String pkg) { 661 try { 662 ApplicationInfo info = mPm.getApplicationInfo(pkg, 663 PackageManager.MATCH_ANY_USER); 664 if (info != null) return mPm.getApplicationLabel(info); 665 } catch (PackageManager.NameNotFoundException e) { 666 Log.e(TAG, "Cannot load package name", e); 667 } 668 return pkg; 669 } 670 671 private static class HistoricalNotificationPreference extends Preference { 672 private final HistoricalNotificationInfo mInfo; 673 private static long sLastExpandedTimestamp; // quick hack to keep things from collapsing 674 public ViewGroup mItemView; // hack to update prefs fast; 675 private Context mContext; 676 HistoricalNotificationPreference(Context context, HistoricalNotificationInfo info, int order)677 public HistoricalNotificationPreference(Context context, HistoricalNotificationInfo info, 678 int order) { 679 super(context); 680 setLayoutResource(R.layout.notification_log_row); 681 setOrder(order); 682 setKey(info.key); 683 mInfo = info; 684 mContext = context; 685 } 686 687 @Override onBindViewHolder(PreferenceViewHolder row)688 public void onBindViewHolder(PreferenceViewHolder row) { 689 super.onBindViewHolder(row); 690 691 mItemView = (ViewGroup) row.itemView; 692 693 updatePreference(mInfo); 694 695 row.findViewById(R.id.timestamp).setOnLongClickListener(v -> { 696 final View extras = row.findViewById(R.id.extra); 697 extras.setVisibility(extras.getVisibility() == View.VISIBLE 698 ? View.GONE : View.VISIBLE); 699 sLastExpandedTimestamp = mInfo.timestamp; 700 return false; 701 }); 702 } 703 updatePreference(HistoricalNotificationInfo info)704 public void updatePreference(HistoricalNotificationInfo info) { 705 if (mItemView == null) { 706 return; 707 } 708 if (info.icon != null) { 709 ((ImageView) mItemView.findViewById(R.id.icon)).setImageDrawable(mInfo.icon); 710 } 711 ((TextView) mItemView.findViewById(R.id.pkgname)).setText(mInfo.pkgname); 712 ((DateTimeView) mItemView.findViewById(R.id.timestamp)).setTime(info.timestamp); 713 if (!TextUtils.isEmpty(info.title)) { 714 ((TextView) mItemView.findViewById(R.id.title)).setText(info.title); 715 mItemView.findViewById(R.id.title).setVisibility(View.VISIBLE); 716 } else { 717 mItemView.findViewById(R.id.title).setVisibility(View.GONE); 718 } 719 if (!TextUtils.isEmpty(info.text)) { 720 ((TextView) mItemView.findViewById(R.id.text)).setText(info.text); 721 mItemView.findViewById(R.id.text).setVisibility(View.VISIBLE); 722 } else { 723 mItemView.findViewById(R.id.text).setVisibility(View.GONE); 724 } 725 if (info.icon != null) { 726 ((ImageView) mItemView.findViewById(R.id.icon)).setImageDrawable(info.icon); 727 } 728 729 ImageView profileBadge = mItemView.findViewById(R.id.profile_badge); 730 Drawable profile = mContext.getPackageManager().getUserBadgeForDensity( 731 UserHandle.of(info.user), -1); 732 profileBadge.setImageDrawable(profile); 733 profileBadge.setVisibility(info.badged ? View.VISIBLE : View.GONE); 734 735 ((DateTimeView) mItemView.findViewById(R.id.timestamp)).setTime(mInfo.timestamp); 736 737 ((TextView) mItemView.findViewById(R.id.notification_extra)) 738 .setText(mInfo.notificationExtra); 739 ((TextView) mItemView.findViewById(R.id.ranking_extra)) 740 .setText(mInfo.rankingExtra); 741 742 mItemView.findViewById(R.id.extra).setVisibility( 743 mInfo.timestamp == sLastExpandedTimestamp ? View.VISIBLE : View.GONE); 744 745 mItemView.setAlpha(mInfo.active ? 1.0f : 0.5f); 746 747 mItemView.findViewById(R.id.alerted_icon).setVisibility( 748 mInfo.alerted ? View.VISIBLE : View.GONE); 749 } 750 751 @Override performClick()752 public void performClick() { 753 Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) 754 .putExtra(EXTRA_APP_PACKAGE, mInfo.pkg) 755 .putExtra(EXTRA_CHANNEL_ID, 756 mInfo.channel != null ? mInfo.channel.getId() : mInfo.channelId); 757 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 758 getContext().startActivity(intent); 759 } 760 } 761 } 762