NotificationStation.java revision 7a038ebb1cebcf60c54ff588770d882775abb4df
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 17package com.android.settings.notification; 18 19import android.app.*; 20import android.app.INotificationManager; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentSender; 25import android.content.pm.ApplicationInfo; 26import android.content.pm.PackageManager; 27import android.content.res.Resources; 28import android.graphics.Typeface; 29import android.graphics.drawable.Drawable; 30import android.net.Uri; 31import android.os.*; 32import android.service.notification.NotificationListenerService; 33import android.service.notification.NotificationListenerService.Ranking; 34import android.service.notification.NotificationListenerService.RankingMap; 35import android.service.notification.StatusBarNotification; 36import android.support.v7.preference.Preference; 37import android.support.v7.preference.PreferenceViewHolder; 38import android.support.v7.widget.RecyclerView; 39import android.text.Spannable; 40import android.text.SpannableString; 41import android.text.SpannableStringBuilder; 42import android.text.TextUtils; 43import android.text.style.StyleSpan; 44import android.util.Log; 45import android.view.View; 46import android.widget.DateTimeView; 47import android.widget.ImageView; 48import android.widget.TextView; 49 50import com.android.internal.logging.MetricsProto.MetricsEvent; 51import com.android.settings.CopyablePreference; 52import com.android.settings.R; 53import com.android.settings.SettingsPreferenceFragment; 54import com.android.settings.Utils; 55 56import java.lang.StringBuilder; 57import java.util.*; 58 59public class NotificationStation extends SettingsPreferenceFragment { 60 private static final String TAG = NotificationStation.class.getSimpleName(); 61 62 private static final boolean DEBUG = false; 63 private static final boolean DUMP_EXTRAS = true; 64 private static final boolean DUMP_PARCEL = true; 65 66 private static class HistoricalNotificationInfo { 67 public String pkg; 68 public Drawable pkgicon; 69 public CharSequence pkgname; 70 public Drawable icon; 71 public CharSequence title; 72 public int priority; 73 public int user; 74 public long timestamp; 75 public boolean active; 76 public CharSequence extra; 77 } 78 79 private PackageManager mPm; 80 private INotificationManager mNoMan; 81 private RankingMap mRanking; 82 83 private Runnable mRefreshListRunnable = new Runnable() { 84 @Override 85 public void run() { 86 refreshList(); 87 } 88 }; 89 90 private NotificationListenerService mListener = new NotificationListenerService() { 91 @Override 92 public void onNotificationPosted(StatusBarNotification sbn, RankingMap ranking) { 93 logd("onNotificationPosted: %s", sbn.getNotification()); 94 final Handler h = getListView().getHandler(); 95 mRanking = ranking; 96 h.removeCallbacks(mRefreshListRunnable); 97 h.postDelayed(mRefreshListRunnable, 100); 98 } 99 100 @Override 101 public void onNotificationRemoved(StatusBarNotification notification, RankingMap ranking) { 102 final Handler h = getListView().getHandler(); 103 mRanking = ranking; 104 h.removeCallbacks(mRefreshListRunnable); 105 h.postDelayed(mRefreshListRunnable, 100); 106 } 107 108 @Override 109 public void onNotificationRankingUpdate(RankingMap ranking) { 110 mRanking = ranking; 111 } 112 }; 113 114 private Context mContext; 115 116 private final Comparator<HistoricalNotificationInfo> mNotificationSorter 117 = new Comparator<HistoricalNotificationInfo>() { 118 @Override 119 public int compare(HistoricalNotificationInfo lhs, 120 HistoricalNotificationInfo rhs) { 121 return (int)(rhs.timestamp - lhs.timestamp); 122 } 123 }; 124 125 @Override 126 public void onAttach(Activity activity) { 127 logd("onAttach(%s)", activity.getClass().getSimpleName()); 128 super.onAttach(activity); 129 mContext = activity; 130 mPm = mContext.getPackageManager(); 131 mNoMan = INotificationManager.Stub.asInterface( 132 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 133 try { 134 mListener.registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(), 135 this.getClass().getCanonicalName()), ActivityManager.getCurrentUser()); 136 } catch (RemoteException e) { 137 Log.e(TAG, "Cannot register listener", e); 138 } 139 } 140 141 @Override 142 public void onDetach() { 143 try { 144 mListener.unregisterAsSystemService(); 145 } catch (RemoteException e) { 146 Log.e(TAG, "Cannot unregister listener", e); 147 } 148 super.onDetach(); 149 } 150 151 @Override 152 protected int getMetricsCategory() { 153 return MetricsEvent.NOTIFICATION_STATION; 154 } 155 156 @Override 157 public void onActivityCreated(Bundle savedInstanceState) { 158 logd("onActivityCreated(%s)", savedInstanceState); 159 super.onActivityCreated(savedInstanceState); 160 161 RecyclerView listView = getListView(); 162 Utils.forceCustomPadding(listView, false /* non additive padding */); 163 } 164 165 @Override 166 public void onResume() { 167 logd("onResume()"); 168 super.onResume(); 169 refreshList(); 170 } 171 172 private void refreshList() { 173 List<HistoricalNotificationInfo> infos = loadNotifications(); 174 if (infos != null) { 175 final int N = infos.size(); 176 logd("adding %d infos", N); 177 Collections.sort(infos, mNotificationSorter); 178 if (getPreferenceScreen() == null) { 179 setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext())); 180 } 181 getPreferenceScreen().removeAll(); 182 for (int i = 0; i < N; i++) { 183 getPreferenceScreen().addPreference( 184 new HistoricalNotificationPreference(getPrefContext(), infos.get(i))); 185 } 186 } 187 } 188 189 private static void logd(String msg, Object... args) { 190 if (DEBUG) { 191 Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); 192 } 193 } 194 195 private static CharSequence bold(CharSequence cs) { 196 if (cs.length() == 0) return cs; 197 SpannableString ss = new SpannableString(cs); 198 ss.setSpan(new StyleSpan(Typeface.BOLD), 0, cs.length(), 0); 199 return ss; 200 } 201 202 private static String getTitleString(Notification n) { 203 String title = null; 204 if (n.extras != null) { 205 title = n.extras.getString(Notification.EXTRA_TITLE); 206 if (TextUtils.isEmpty(title)) { 207 title = n.extras.getString(Notification.EXTRA_TEXT); 208 } 209 } 210 if (TextUtils.isEmpty(title) && !TextUtils.isEmpty(n.tickerText)) { 211 title = n.tickerText.toString(); 212 } 213 return title; 214 } 215 216 private static String formatPendingIntent(PendingIntent pi) { 217 final StringBuilder sb = new StringBuilder(); 218 final IntentSender is = pi.getIntentSender(); 219 sb.append("Intent(pkg=").append(is.getCreatorPackage()); 220 try { 221 final boolean isActivity = 222 ActivityManagerNative.getDefault().isIntentSenderAnActivity(is.getTarget()); 223 if (isActivity) sb.append(" (activity)"); 224 } catch (RemoteException ex) {} 225 sb.append(")"); 226 return sb.toString(); 227 } 228 229 private List<HistoricalNotificationInfo> loadNotifications() { 230 final int currentUserId = ActivityManager.getCurrentUser(); 231 try { 232 StatusBarNotification[] active = mNoMan.getActiveNotifications( 233 mContext.getPackageName()); 234 StatusBarNotification[] dismissed = mNoMan.getHistoricalNotifications( 235 mContext.getPackageName(), 50); 236 237 List<HistoricalNotificationInfo> list 238 = new ArrayList<HistoricalNotificationInfo>(active.length + dismissed.length); 239 240 final Ranking rank = new Ranking(); 241 242 for (StatusBarNotification[] resultset 243 : new StatusBarNotification[][] { active, dismissed }) { 244 for (StatusBarNotification sbn : resultset) { 245 if (sbn.getUserId() != UserHandle.USER_ALL & sbn.getUserId() != currentUserId) { 246 continue; 247 } 248 249 final Notification n = sbn.getNotification(); 250 final HistoricalNotificationInfo info = new HistoricalNotificationInfo(); 251 info.pkg = sbn.getPackageName(); 252 info.user = sbn.getUserId(); 253 info.icon = loadIconDrawable(info.pkg, info.user, n.icon); 254 info.pkgicon = loadPackageIconDrawable(info.pkg, info.user); 255 info.pkgname = loadPackageName(info.pkg); 256 info.title = getTitleString(n); 257 if (TextUtils.isEmpty(info.title)) { 258 info.title = getString(R.string.notification_log_no_title); 259 } 260 info.timestamp = sbn.getPostTime(); 261 info.priority = n.priority; 262 263 info.active = (resultset == active); 264 265 final SpannableStringBuilder sb = new SpannableStringBuilder(); 266 final String delim = getString(R.string.notification_log_details_delimiter); 267 sb.append(bold(getString(R.string.notification_log_details_package))) 268 .append(delim) 269 .append(info.pkg) 270 .append("\n") 271 .append(bold(getString(R.string.notification_log_details_key))) 272 .append(delim) 273 .append(sbn.getKey()); 274 sb.append("\n") 275 .append(bold(getString(R.string.notification_log_details_icon))) 276 .append(delim) 277 .append(n.getSmallIcon().toString()); 278 if (!TextUtils.isEmpty(n.getGroup())) { 279 sb.append("\n") 280 .append(bold(getString(R.string.notification_log_details_group))) 281 .append(delim) 282 .append(n.getGroup()); 283 if (n.isGroupSummary()) { 284 sb.append(bold( 285 getString(R.string.notification_log_details_group_summary))); 286 } 287 } 288 sb.append("\n") 289 .append(bold(getString(R.string.notification_log_details_sound))) 290 .append(delim); 291 if (0 != (n.defaults & Notification.DEFAULT_SOUND)) { 292 sb.append(getString(R.string.notification_log_details_default)); 293 } else if (n.sound != null) { 294 sb.append(n.sound.toString()); 295 } else { 296 sb.append(getString(R.string.notification_log_details_none)); 297 } 298 sb.append("\n") 299 .append(bold(getString(R.string.notification_log_details_vibrate))) 300 .append(delim); 301 if (0 != (n.defaults & Notification.DEFAULT_VIBRATE)) { 302 sb.append(getString(R.string.notification_log_details_default)); 303 } else if (n.vibrate != null) { 304 for (int vi=0;vi<n.vibrate.length;vi++) { 305 if (vi > 0) sb.append(','); 306 sb.append(String.valueOf(n.vibrate[vi])); 307 } 308 } else { 309 sb.append(getString(R.string.notification_log_details_none)); 310 } 311 sb.append("\n") 312 .append(bold(getString(R.string.notification_log_details_visibility))) 313 .append(delim) 314 .append(Notification.visibilityToString(n.visibility)); 315 if (n.publicVersion != null) { 316 sb.append("\n") 317 .append(bold(getString( 318 R.string.notification_log_details_public_version))) 319 .append(delim) 320 .append(getTitleString(n.publicVersion)); 321 } 322 sb.append("\n") 323 .append(bold(getString(R.string.notification_log_details_priority))) 324 .append(delim) 325 .append(Notification.priorityToString(n.priority)); 326 if (mRanking != null && mRanking.getRanking(sbn.getKey(), rank)) { 327 sb.append("\n") 328 .append(bold(getString( 329 R.string.notification_log_details_importance))) 330 .append(delim) 331 .append(Ranking.importanceToString(rank.getImportance())); 332 if (rank.getImportanceExplanation() != null) { 333 sb.append("\n") 334 .append(bold(getString( 335 R.string.notification_log_details_explanation))) 336 .append(delim) 337 .append(rank.getImportanceExplanation()); 338 } 339 } 340 if (n.contentIntent != null) { 341 sb.append("\n") 342 .append(bold(getString( 343 R.string.notification_log_details_content_intent))) 344 .append(delim) 345 .append(formatPendingIntent(n.contentIntent)); 346 } 347 if (n.deleteIntent != null) { 348 sb.append("\n") 349 .append(bold(getString( 350 R.string.notification_log_details_delete_intent))) 351 .append(delim) 352 .append(formatPendingIntent(n.deleteIntent)); 353 } 354 if (n.fullScreenIntent != null) { 355 sb.append("\n") 356 .append(bold(getString( 357 R.string.notification_log_details_full_screen_intent))) 358 .append(delim) 359 .append(formatPendingIntent(n.fullScreenIntent)); 360 } 361 if (n.actions != null && n.actions.length > 0) { 362 sb.append("\n") 363 .append(bold(getString(R.string.notification_log_details_actions))); 364 for (int ai=0; ai<n.actions.length; ai++) { 365 final Notification.Action action = n.actions[ai]; 366 sb.append("\n ").append(String.valueOf(ai)).append(' ') 367 .append(bold(getString( 368 R.string.notification_log_details_title))) 369 .append(delim) 370 .append(action.title) 371 .append("\n ") 372 .append(bold(getString( 373 R.string.notification_log_details_content_intent))) 374 .append(delim) 375 .append(formatPendingIntent(action.actionIntent)); 376 if (action.getRemoteInputs() != null) { 377 sb.append(' ') 378 .append(bold(getString( 379 R.string.notification_log_details_remoteinput))) 380 .append(delim) 381 .append(String.valueOf(action.getRemoteInputs().length)); 382 } 383 } 384 } 385 if (n.contentView != null) { 386 sb.append("\n") 387 .append(bold(getString( 388 R.string.notification_log_details_content_view))) 389 .append(delim) 390 .append(n.contentView.toString()); 391 } 392 393 if (DUMP_EXTRAS) { 394 if (n.extras != null && n.extras.size() > 0) { 395 sb.append("\n") 396 .append(bold(getString( 397 R.string.notification_log_details_extras))); 398 for (String extraKey : n.extras.keySet()) { 399 String val = String.valueOf(n.extras.get(extraKey)); 400 if (val.length() > 100) val = val.substring(0, 100) + "..."; 401 sb.append("\n ").append(extraKey).append(delim).append(val); 402 } 403 } 404 } 405 if (DUMP_PARCEL) { 406 final Parcel p = Parcel.obtain(); 407 n.writeToParcel(p, 0); 408 sb.append("\n") 409 .append(bold(getString(R.string.notification_log_details_parcel))) 410 .append(delim) 411 .append(String.valueOf(p.dataPosition())) 412 .append(' ') 413 .append(bold(getString(R.string.notification_log_details_ashmem))) 414 .append(delim) 415 .append(String.valueOf(p.getBlobAshmemSize())) 416 .append("\n"); 417 } 418 419 info.extra = sb; 420 421 logd(" [%d] %s: %s", info.timestamp, info.pkg, info.title); 422 list.add(info); 423 } 424 } 425 426 return list; 427 } catch (RemoteException e) { 428 Log.e(TAG, "Cannot load Notifications: ", e); 429 } 430 return null; 431 } 432 433 private Resources getResourcesForUserPackage(String pkg, int userId) { 434 Resources r = null; 435 436 if (pkg != null) { 437 try { 438 if (userId == UserHandle.USER_ALL) { 439 userId = UserHandle.USER_SYSTEM; 440 } 441 r = mPm.getResourcesForApplicationAsUser(pkg, userId); 442 } catch (PackageManager.NameNotFoundException ex) { 443 Log.e(TAG, "Icon package not found: " + pkg, ex); 444 return null; 445 } 446 } else { 447 r = mContext.getResources(); 448 } 449 return r; 450 } 451 452 private Drawable loadPackageIconDrawable(String pkg, int userId) { 453 Drawable icon = null; 454 try { 455 icon = mPm.getApplicationIcon(pkg); 456 } catch (PackageManager.NameNotFoundException e) { 457 Log.e(TAG, "Cannot get application icon", e); 458 } 459 460 return icon; 461 } 462 463 private CharSequence loadPackageName(String pkg) { 464 try { 465 ApplicationInfo info = mPm.getApplicationInfo(pkg, 466 PackageManager.GET_UNINSTALLED_PACKAGES); 467 if (info != null) return mPm.getApplicationLabel(info); 468 } catch (PackageManager.NameNotFoundException e) { 469 Log.e(TAG, "Cannot load package name", e); 470 } 471 return pkg; 472 } 473 474 private Drawable loadIconDrawable(String pkg, int userId, int resId) { 475 Resources r = getResourcesForUserPackage(pkg, userId); 476 477 if (resId == 0) { 478 return null; 479 } 480 481 try { 482 return r.getDrawable(resId, null); 483 } catch (RuntimeException e) { 484 Log.w(TAG, "Icon not found in " 485 + (pkg != null ? resId : "<system>") 486 + ": " + Integer.toHexString(resId), e); 487 } 488 489 return null; 490 } 491 492 private static class HistoricalNotificationPreference extends CopyablePreference { 493 private final HistoricalNotificationInfo mInfo; 494 495 public HistoricalNotificationPreference(Context context, HistoricalNotificationInfo info) { 496 super(context); 497 setLayoutResource(R.layout.notification_log_row); 498 mInfo = info; 499 } 500 501 @Override 502 public void onBindViewHolder(PreferenceViewHolder row) { 503 super.onBindViewHolder(row); 504 505 if (mInfo.icon != null) { 506 ((ImageView) row.findViewById(R.id.icon)).setImageDrawable(mInfo.icon); 507 } 508 if (mInfo.pkgicon != null) { 509 ((ImageView) row.findViewById(R.id.pkgicon)).setImageDrawable(mInfo.pkgicon); 510 } 511 512 ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(mInfo.timestamp); 513 ((TextView) row.findViewById(R.id.title)).setText(mInfo.title); 514 ((TextView) row.findViewById(R.id.pkgname)).setText(mInfo.pkgname); 515 516 final TextView extra = (TextView) row.findViewById(R.id.extra); 517 extra.setText(mInfo.extra); 518 extra.setVisibility(View.GONE); 519 520 row.itemView.setOnClickListener( 521 new View.OnClickListener() { 522 @Override 523 public void onClick(View view) { 524 extra.setVisibility(extra.getVisibility() == View.VISIBLE 525 ? View.GONE : View.VISIBLE); 526 } 527 }); 528 529 row.itemView.setAlpha(mInfo.active ? 1.0f : 0.5f); 530 } 531 532 @Override 533 public CharSequence getCopyableText() { 534 return new SpannableStringBuilder(mInfo.title) 535 .append(" [").append(new Date(mInfo.timestamp).toString()) 536 .append("]\n").append(mInfo.pkgname) 537 .append("\n").append(mInfo.extra); 538 } 539 540 @Override 541 public void performClick() { 542// Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 543// Uri.fromParts("package", mInfo.pkg, null)); 544// intent.setComponent(intent.resolveActivity(getContext().getPackageManager())); 545// getContext().startActivity(intent); 546 } 547 } 548} 549