BaseStatusBar.java revision c7d62f02b8acfd0a6b31f8544ec2c07e780fe4bb
1/* 2 * Copyright (C) 2010 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.systemui.statusbar; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.TimeInterpolator; 22import android.app.ActivityManager; 23import android.app.ActivityManagerNative; 24import android.app.Notification; 25import android.app.NotificationManager; 26import android.app.PendingIntent; 27import android.app.TaskStackBuilder; 28import android.app.admin.DevicePolicyManager; 29import android.content.BroadcastReceiver; 30import android.content.ComponentName; 31import android.content.Context; 32import android.content.Intent; 33import android.content.IntentFilter; 34import android.content.pm.ApplicationInfo; 35import android.content.pm.PackageManager; 36import android.content.pm.PackageManager.NameNotFoundException; 37import android.content.pm.ResolveInfo; 38import android.content.pm.UserInfo; 39import android.content.res.Configuration; 40import android.content.res.Resources; 41import android.database.ContentObserver; 42import android.graphics.drawable.Drawable; 43import android.os.AsyncTask; 44import android.os.Build; 45import android.os.Handler; 46import android.os.IBinder; 47import android.os.Message; 48import android.os.PowerManager; 49import android.os.RemoteException; 50import android.os.ServiceManager; 51import android.os.UserHandle; 52import android.os.UserManager; 53import android.provider.Settings; 54import android.service.dreams.DreamService; 55import android.service.dreams.IDreamManager; 56import android.service.notification.NotificationListenerService; 57import android.service.notification.NotificationListenerService.RankingMap; 58import android.service.notification.StatusBarNotification; 59import android.text.TextUtils; 60import android.util.Log; 61import android.util.SparseArray; 62import android.util.SparseBooleanArray; 63import android.view.Display; 64import android.view.IWindowManager; 65import android.view.LayoutInflater; 66import android.view.MotionEvent; 67import android.view.View; 68import android.view.ViewAnimationUtils; 69import android.view.ViewGroup; 70import android.view.ViewGroup.LayoutParams; 71import android.view.WindowManager; 72import android.view.WindowManagerGlobal; 73import android.view.animation.AnimationUtils; 74import android.widget.DateTimeView; 75import android.widget.ImageView; 76import android.widget.LinearLayout; 77import android.widget.RemoteViews; 78import android.widget.TextView; 79 80import com.android.internal.statusbar.IStatusBarService; 81import com.android.internal.statusbar.StatusBarIcon; 82import com.android.internal.statusbar.StatusBarIconList; 83import com.android.internal.util.NotificationColorUtil; 84import com.android.internal.widget.LockPatternUtils; 85import com.android.systemui.R; 86import com.android.systemui.RecentsComponent; 87import com.android.systemui.SearchPanelView; 88import com.android.systemui.SwipeHelper; 89import com.android.systemui.SystemUI; 90import com.android.systemui.statusbar.NotificationData.Entry; 91import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; 92import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 93import com.android.systemui.statusbar.policy.HeadsUpNotificationView; 94import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 95 96import java.util.ArrayList; 97import java.util.List; 98import java.util.Locale; 99 100import static com.android.keyguard.KeyguardHostView.OnDismissAction; 101 102public abstract class BaseStatusBar extends SystemUI implements 103 CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener, 104 RecentsComponent.Callbacks, ExpandableNotificationRow.ExpansionLogger, 105 NotificationData.Environment { 106 public static final String TAG = "StatusBar"; 107 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 108 public static final boolean MULTIUSER_DEBUG = false; 109 110 protected static final int MSG_SHOW_RECENT_APPS = 1019; 111 protected static final int MSG_HIDE_RECENT_APPS = 1020; 112 protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; 113 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 114 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 115 protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024; 116 protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025; 117 protected static final int MSG_CLOSE_SEARCH_PANEL = 1027; 118 protected static final int MSG_SHOW_HEADS_UP = 1028; 119 protected static final int MSG_HIDE_HEADS_UP = 1029; 120 protected static final int MSG_ESCALATE_HEADS_UP = 1030; 121 protected static final int MSG_DECAY_HEADS_UP = 1031; 122 123 protected static final boolean ENABLE_HEADS_UP = true; 124 // scores above this threshold should be displayed in heads up mode. 125 protected static final int INTERRUPTION_THRESHOLD = 10; 126 protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; 127 128 // Should match the value in PhoneWindowManager 129 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 130 131 public static final int EXPANDED_LEAVE_ALONE = -10000; 132 public static final int EXPANDED_FULL_OPEN = -10001; 133 134 private static final int HIDDEN_NOTIFICATION_ID = 10000; 135 private static final String BANNER_ACTION_CANCEL = 136 "com.android.systemui.statusbar.banner_action_cancel"; 137 private static final String BANNER_ACTION_SETUP = 138 "com.android.systemui.statusbar.banner_action_setup"; 139 140 protected CommandQueue mCommandQueue; 141 protected IStatusBarService mBarService; 142 protected H mHandler = createHandler(); 143 144 // all notifications 145 protected NotificationData mNotificationData; 146 protected NotificationStackScrollLayout mStackScroller; 147 148 // for heads up notifications 149 protected HeadsUpNotificationView mHeadsUpNotificationView; 150 protected int mHeadsUpNotificationDecay; 151 152 // used to notify status bar for suppressing notification LED 153 protected boolean mPanelSlightlyVisible; 154 155 // Search panel 156 protected SearchPanelView mSearchPanelView; 157 158 protected int mCurrentUserId = 0; 159 final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); 160 161 protected int mLayoutDirection = -1; // invalid 162 private Locale mLocale; 163 private float mFontScale; 164 165 protected boolean mUseHeadsUp = false; 166 protected boolean mHeadsUpTicker = false; 167 protected boolean mDisableNotificationAlerts = false; 168 169 protected DevicePolicyManager mDevicePolicyManager; 170 protected IDreamManager mDreamManager; 171 PowerManager mPowerManager; 172 protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 173 protected int mRowMinHeight; 174 protected int mRowMaxHeight; 175 176 // public mode, private notifications, etc 177 private boolean mLockscreenPublicMode = false; 178 private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); 179 private NotificationColorUtil mNotificationColorUtil; 180 181 private UserManager mUserManager; 182 183 // UI-specific methods 184 185 /** 186 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 187 * and add them to the window manager. 188 */ 189 protected abstract void createAndAddWindows(); 190 191 protected WindowManager mWindowManager; 192 protected IWindowManager mWindowManagerService; 193 194 protected abstract void refreshLayout(int layoutDirection); 195 196 protected Display mDisplay; 197 198 private boolean mDeviceProvisioned = false; 199 200 private RecentsComponent mRecents; 201 202 protected int mZenMode; 203 204 // which notification is currently being longpress-examined by the user 205 private NotificationGuts mNotificationGutsExposed; 206 207 private TimeInterpolator mLinearOutSlowIn, mFastOutLinearIn; 208 209 /** 210 * The {@link StatusBarState} of the status bar. 211 */ 212 protected int mState; 213 protected boolean mBouncerShowing; 214 protected boolean mShowLockscreenNotifications; 215 216 protected NotificationOverflowContainer mKeyguardIconOverflowContainer; 217 protected DismissView mDismissView; 218 protected EmptyShadeView mEmptyShadeView; 219 220 @Override // NotificationData.Environment 221 public boolean isDeviceProvisioned() { 222 return mDeviceProvisioned; 223 } 224 225 protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 226 @Override 227 public void onChange(boolean selfChange) { 228 final boolean provisioned = 0 != Settings.Global.getInt( 229 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); 230 if (provisioned != mDeviceProvisioned) { 231 mDeviceProvisioned = provisioned; 232 updateNotifications(); 233 } 234 final int mode = Settings.Global.getInt(mContext.getContentResolver(), 235 Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); 236 setZenMode(mode); 237 238 updateLockscreenNotificationSetting(); 239 } 240 }; 241 242 private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) { 243 @Override 244 public void onChange(boolean selfChange) { 245 // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 246 // so we just dump our cache ... 247 mUsersAllowingPrivateNotifications.clear(); 248 // ... and refresh all the notifications 249 updateNotifications(); 250 } 251 }; 252 253 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { 254 @Override 255 public boolean onClickHandler( 256 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { 257 if (DEBUG) { 258 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); 259 } 260 final boolean isActivity = pendingIntent.isActivity(); 261 if (isActivity) { 262 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 263 dismissKeyguardThenExecute(new OnDismissAction() { 264 @Override 265 public boolean onDismiss() { 266 if (keyguardShowing) { 267 try { 268 ActivityManagerNative.getDefault() 269 .keyguardWaitingForActivityDrawn(); 270 // The intent we are sending is for the application, which 271 // won't have permission to immediately start an activity after 272 // the user switches to home. We know it is safe to do at this 273 // point, so make sure new activity switches are now allowed. 274 ActivityManagerNative.getDefault().resumeAppSwitches(); 275 } catch (RemoteException e) { 276 } 277 } 278 279 boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent); 280 overrideActivityPendingAppTransition(keyguardShowing); 281 282 // close the shade if it was open 283 if (handled) { 284 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 285 visibilityChanged(false); 286 } 287 // Wait for activity start. 288 return handled; 289 } 290 }, false /* afterKeyguardGone */); 291 return true; 292 } else { 293 return super.onClickHandler(view, pendingIntent, fillInIntent); 294 } 295 } 296 297 private boolean superOnClickHandler(View view, PendingIntent pendingIntent, 298 Intent fillInIntent) { 299 return super.onClickHandler(view, pendingIntent, fillInIntent); 300 } 301 }; 302 303 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 304 @Override 305 public void onReceive(Context context, Intent intent) { 306 String action = intent.getAction(); 307 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 308 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 309 updateCurrentProfilesCache(); 310 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); 311 312 updateLockscreenNotificationSetting(); 313 314 userSwitched(mCurrentUserId); 315 } else if (Intent.ACTION_USER_ADDED.equals(action)) { 316 updateCurrentProfilesCache(); 317 } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals( 318 action)) { 319 mUsersAllowingPrivateNotifications.clear(); 320 updateLockscreenNotificationSetting(); 321 updateNotifications(); 322 } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { 323 NotificationManager noMan = (NotificationManager) 324 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 325 noMan.cancel(HIDDEN_NOTIFICATION_ID); 326 327 Settings.Secure.putInt(mContext.getContentResolver(), 328 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); 329 if (BANNER_ACTION_SETUP.equals(action)) { 330 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 331 mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) 332 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 333 334 ); 335 } 336 } 337 } 338 }; 339 340 private final NotificationListenerService mNotificationListener = 341 new NotificationListenerService() { 342 @Override 343 public void onListenerConnected() { 344 if (DEBUG) Log.d(TAG, "onListenerConnected"); 345 final StatusBarNotification[] notifications = getActiveNotifications(); 346 final RankingMap currentRanking = getCurrentRanking(); 347 mHandler.post(new Runnable() { 348 @Override 349 public void run() { 350 for (StatusBarNotification sbn : notifications) { 351 addNotification(sbn, currentRanking); 352 } 353 } 354 }); 355 } 356 357 @Override 358 public void onNotificationPosted(final StatusBarNotification sbn, 359 final RankingMap rankingMap) { 360 if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); 361 mHandler.post(new Runnable() { 362 @Override 363 public void run() { 364 Notification n = sbn.getNotification(); 365 boolean isUpdate = mNotificationData.get(sbn.getKey()) != null 366 || isHeadsUp(sbn.getKey()); 367 368 // Ignore children of notifications that have a summary, since we're not 369 // going to show them anyway. This is true also when the summary is canceled, 370 // because children are automatically canceled by NoMan in that case. 371 if (n.isGroupChild() && 372 mNotificationData.isGroupWithSummary(sbn.getGroupKey())) { 373 if (DEBUG) { 374 Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); 375 } 376 377 // Remove existing notification to avoid stale data. 378 if (isUpdate) { 379 removeNotification(sbn.getKey(), rankingMap); 380 } else { 381 mNotificationData.updateRanking(rankingMap); 382 } 383 return; 384 } 385 if (isUpdate) { 386 updateNotification(sbn, rankingMap); 387 } else { 388 addNotification(sbn, rankingMap); 389 } 390 } 391 }); 392 } 393 394 @Override 395 public void onNotificationRemoved(final StatusBarNotification sbn, 396 final RankingMap rankingMap) { 397 if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); 398 mHandler.post(new Runnable() { 399 @Override 400 public void run() { 401 removeNotification(sbn.getKey(), rankingMap); 402 } 403 }); 404 } 405 406 @Override 407 public void onNotificationRankingUpdate(final RankingMap rankingMap) { 408 if (DEBUG) Log.d(TAG, "onRankingUpdate"); 409 mHandler.post(new Runnable() { 410 @Override 411 public void run() { 412 updateNotificationRanking(rankingMap); 413 } 414 }); 415 } 416 417 }; 418 419 private void updateCurrentProfilesCache() { 420 synchronized (mCurrentProfiles) { 421 mCurrentProfiles.clear(); 422 if (mUserManager != null) { 423 for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { 424 mCurrentProfiles.put(user.id, user); 425 } 426 } 427 } 428 } 429 430 public void start() { 431 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 432 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 433 mDisplay = mWindowManager.getDefaultDisplay(); 434 mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService( 435 Context.DEVICE_POLICY_SERVICE); 436 437 mNotificationColorUtil = NotificationColorUtil.getInstance(mContext); 438 439 mNotificationData = new NotificationData(this); 440 441 mDreamManager = IDreamManager.Stub.asInterface( 442 ServiceManager.checkService(DreamService.DREAM_SERVICE)); 443 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 444 445 mSettingsObserver.onChange(false); // set up 446 mContext.getContentResolver().registerContentObserver( 447 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, 448 mSettingsObserver); 449 mContext.getContentResolver().registerContentObserver( 450 Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, 451 mSettingsObserver); 452 mContext.getContentResolver().registerContentObserver( 453 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, 454 mSettingsObserver, 455 UserHandle.USER_ALL); 456 457 mContext.getContentResolver().registerContentObserver( 458 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), 459 true, 460 mLockscreenSettingsObserver, 461 UserHandle.USER_ALL); 462 463 mBarService = IStatusBarService.Stub.asInterface( 464 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 465 466 mRecents = getComponent(RecentsComponent.class); 467 mRecents.setCallback(this); 468 469 final Configuration currentConfig = mContext.getResources().getConfiguration(); 470 mLocale = currentConfig.locale; 471 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 472 mFontScale = currentConfig.fontScale; 473 474 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 475 476 mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext, 477 android.R.interpolator.linear_out_slow_in); 478 mFastOutLinearIn = AnimationUtils.loadInterpolator(mContext, 479 android.R.interpolator.fast_out_linear_in); 480 481 // Connect in to the status bar manager service 482 StatusBarIconList iconList = new StatusBarIconList(); 483 mCommandQueue = new CommandQueue(this, iconList); 484 485 int[] switches = new int[8]; 486 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 487 try { 488 mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders); 489 } catch (RemoteException ex) { 490 // If the system process isn't there we're doomed anyway. 491 } 492 493 createAndAddWindows(); 494 495 disable(switches[0], false /* animate */); 496 setSystemUiVisibility(switches[1], 0xffffffff); 497 topAppWindowChanged(switches[2] != 0); 498 // StatusBarManagerService has a back up of IME token and it's restored here. 499 setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0); 500 501 // Set up the initial icon state 502 int N = iconList.size(); 503 int viewIndex = 0; 504 for (int i=0; i<N; i++) { 505 StatusBarIcon icon = iconList.getIcon(i); 506 if (icon != null) { 507 addIcon(iconList.getSlot(i), i, viewIndex, icon); 508 viewIndex++; 509 } 510 } 511 512 // Set up the initial notification state. 513 try { 514 mNotificationListener.registerAsSystemService(mContext, 515 new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), 516 UserHandle.USER_ALL); 517 } catch (RemoteException e) { 518 Log.e(TAG, "Unable to register notification listener", e); 519 } 520 521 522 if (DEBUG) { 523 Log.d(TAG, String.format( 524 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 525 iconList.size(), 526 switches[0], 527 switches[1], 528 switches[2], 529 switches[3] 530 )); 531 } 532 533 mCurrentUserId = ActivityManager.getCurrentUser(); 534 535 IntentFilter filter = new IntentFilter(); 536 filter.addAction(Intent.ACTION_USER_SWITCHED); 537 filter.addAction(Intent.ACTION_USER_ADDED); 538 filter.addAction(BANNER_ACTION_CANCEL); 539 filter.addAction(BANNER_ACTION_SETUP); 540 filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); 541 mContext.registerReceiver(mBroadcastReceiver, filter); 542 543 updateCurrentProfilesCache(); 544 } 545 546 protected void notifyUserAboutHiddenNotifications() { 547 if (0 != Settings.Secure.getInt(mContext.getContentResolver(), 548 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) { 549 Log.d(TAG, "user hasn't seen notification about hidden notifications"); 550 final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); 551 if (!lockPatternUtils.isSecure()) { 552 Log.d(TAG, "insecure lockscreen, skipping notification"); 553 Settings.Secure.putInt(mContext.getContentResolver(), 554 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); 555 return; 556 } 557 Log.d(TAG, "disabling lockecreen notifications and alerting the user"); 558 // disable lockscreen notifications until user acts on the banner. 559 Settings.Secure.putInt(mContext.getContentResolver(), 560 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); 561 Settings.Secure.putInt(mContext.getContentResolver(), 562 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); 563 564 final String packageName = mContext.getPackageName(); 565 PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0, 566 new Intent(BANNER_ACTION_CANCEL).setPackage(packageName), 567 PendingIntent.FLAG_CANCEL_CURRENT); 568 PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0, 569 new Intent(BANNER_ACTION_SETUP).setPackage(packageName), 570 PendingIntent.FLAG_CANCEL_CURRENT); 571 572 final Resources res = mContext.getResources(); 573 final int colorRes = com.android.internal.R.color.system_notification_accent_color; 574 Notification.Builder note = new Notification.Builder(mContext) 575 .setSmallIcon(R.drawable.ic_android) 576 .setContentTitle(mContext.getString(R.string.hidden_notifications_title)) 577 .setContentText(mContext.getString(R.string.hidden_notifications_text)) 578 .setPriority(Notification.PRIORITY_HIGH) 579 .setOngoing(true) 580 .setColor(res.getColor(colorRes)) 581 .setContentIntent(setupIntent) 582 .addAction(R.drawable.ic_close, 583 mContext.getString(R.string.hidden_notifications_cancel), 584 cancelIntent) 585 .addAction(R.drawable.ic_settings, 586 mContext.getString(R.string.hidden_notifications_setup), 587 setupIntent); 588 589 NotificationManager noMan = 590 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 591 noMan.notify(HIDDEN_NOTIFICATION_ID, note.build()); 592 } 593 } 594 595 public void userSwitched(int newUserId) { 596 // should be overridden 597 } 598 599 public boolean isHeadsUp(String key) { 600 return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key); 601 } 602 603 @Override // NotificationData.Environment 604 public boolean isNotificationForCurrentProfiles(StatusBarNotification n) { 605 final int thisUserId = mCurrentUserId; 606 final int notificationUserId = n.getUserId(); 607 if (DEBUG && MULTIUSER_DEBUG) { 608 Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", 609 n, thisUserId, notificationUserId)); 610 } 611 synchronized (mCurrentProfiles) { 612 return notificationUserId == UserHandle.USER_ALL 613 || mCurrentProfiles.get(notificationUserId) != null; 614 } 615 } 616 617 @Override 618 public String getCurrentMediaNotificationKey() { 619 return null; 620 } 621 622 /** 623 * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. 624 * @param action A dismiss action that is called if it's safe to start the activity. 625 * @param afterKeyguardGone Whether the action should be executed after the Keyguard is gone. 626 */ 627 protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { 628 action.onDismiss(); 629 } 630 631 @Override 632 protected void onConfigurationChanged(Configuration newConfig) { 633 final Locale locale = mContext.getResources().getConfiguration().locale; 634 final int ld = TextUtils.getLayoutDirectionFromLocale(locale); 635 final float fontScale = newConfig.fontScale; 636 637 if (! locale.equals(mLocale) || ld != mLayoutDirection || fontScale != mFontScale) { 638 if (DEBUG) { 639 Log.v(TAG, String.format( 640 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, 641 locale, ld)); 642 } 643 mLocale = locale; 644 mLayoutDirection = ld; 645 refreshLayout(ld); 646 } 647 } 648 649 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 650 View vetoButton = row.findViewById(R.id.veto); 651 if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null 652 && mHeadsUpNotificationView.getEntry().row == row)) { 653 final String _pkg = n.getPackageName(); 654 final String _tag = n.getTag(); 655 final int _id = n.getId(); 656 final int _userId = n.getUserId(); 657 vetoButton.setOnClickListener(new View.OnClickListener() { 658 public void onClick(View v) { 659 // Accessibility feedback 660 v.announceForAccessibility( 661 mContext.getString(R.string.accessibility_notification_dismissed)); 662 try { 663 mBarService.onNotificationClear(_pkg, _tag, _id, _userId); 664 665 } catch (RemoteException ex) { 666 // system process is dead if we're here. 667 } 668 } 669 }); 670 vetoButton.setVisibility(View.VISIBLE); 671 } else { 672 vetoButton.setVisibility(View.GONE); 673 } 674 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 675 return vetoButton; 676 } 677 678 679 protected void applyColorsAndBackgrounds(StatusBarNotification sbn, 680 NotificationData.Entry entry) { 681 PackageManager pmUser = getPackageManagerForUser( 682 entry.notification.getUser().getIdentifier()); 683 int version = 0; 684 try { 685 ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); 686 version = info.targetSdkVersion; 687 } catch (NameNotFoundException ex) { 688 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 689 } 690 691 if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) { 692 // Using custom RemoteViews 693 if (version >= Build.VERSION_CODES.GINGERBREAD && version < Build.VERSION_CODES.L) { 694 entry.row.setShowingLegacyBackground(true); 695 entry.legacy = true; 696 } 697 } else { 698 // Using platform templates 699 final int color = sbn.getNotification().color; 700 if (isMediaNotification(entry)) { 701 entry.row.setTintColor(color == Notification.COLOR_DEFAULT 702 ? mContext.getResources().getColor( 703 R.color.notification_material_background_media_default_color) 704 : color); 705 } 706 } 707 708 if (entry.icon != null) { 709 if (version >= Build.VERSION_CODES.L) { 710 entry.icon.setColorFilter(mContext.getResources().getColor(android.R.color.white)); 711 } else { 712 entry.icon.setColorFilter(null); 713 } 714 } 715 } 716 717 public boolean isMediaNotification(NotificationData.Entry entry) { 718 // TODO: confirm that there's a valid media key 719 return entry.expandedBig != null && 720 entry.expandedBig.findViewById(com.android.internal.R.id.media_actions) != null; 721 } 722 723 // The gear button in the guts that links to the app's own notification settings 724 private void startAppOwnNotificationSettingsActivity(Intent intent, 725 final int notificationId, final String notificationTag, final int appUid) { 726 intent.putExtra("notification_id", notificationId); 727 intent.putExtra("notification_tag", notificationTag); 728 startNotificationGutsIntent(intent, appUid); 729 } 730 731 // The (i) button in the guts that links to the system notification settings for that app 732 private void startAppNotificationSettingsActivity(String packageName, final int appUid) { 733 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 734 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); 735 intent.putExtra(Settings.EXTRA_APP_UID, appUid); 736 startNotificationGutsIntent(intent, appUid); 737 } 738 739 private void startNotificationGutsIntent(final Intent intent, final int appUid) { 740 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 741 dismissKeyguardThenExecute(new OnDismissAction() { 742 @Override 743 public boolean onDismiss() { 744 AsyncTask.execute(new Runnable() { 745 public void run() { 746 try { 747 if (keyguardShowing) { 748 ActivityManagerNative.getDefault() 749 .keyguardWaitingForActivityDrawn(); 750 } 751 TaskStackBuilder.create(mContext) 752 .addNextIntentWithParentStack(intent) 753 .startActivities(null, 754 new UserHandle(UserHandle.getUserId(appUid))); 755 overrideActivityPendingAppTransition(keyguardShowing); 756 } catch (RemoteException e) { 757 } 758 } 759 }); 760 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 761 return true; 762 } 763 }, false /* afterKeyguardGone */); 764 } 765 766 protected SwipeHelper.LongPressListener getNotificationLongClicker() { 767 return new SwipeHelper.LongPressListener() { 768 @Override 769 public boolean onLongPress(View v, int x, int y) { 770 dismissPopups(); 771 772 if (v.getWindowToken() == null) { 773 Log.e(TAG, "Trying to show notification guts, but not attached to window"); 774 return false; 775 } 776 777 // Assume we are a status_bar_notification_row 778 final NotificationGuts guts = (NotificationGuts) v.findViewById( 779 R.id.notification_guts); 780 781 // Already showing? 782 if (guts.getVisibility() == View.VISIBLE) { 783 Log.e(TAG, "Trying to show notification guts, but already visible"); 784 return false; 785 } 786 787 guts.setVisibility(View.VISIBLE); 788 final double horz = Math.max(guts.getWidth() - x, x); 789 final double vert = Math.max(guts.getActualHeight() - y, y); 790 final float r = (float) Math.hypot(horz, vert); 791 final Animator a 792 = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r); 793 a.setDuration(400); 794 a.setInterpolator(mLinearOutSlowIn); 795 a.start(); 796 797 mNotificationGutsExposed = guts; 798 799 return true; 800 } 801 }; 802 } 803 804 public void dismissPopups() { 805 if (mNotificationGutsExposed != null) { 806 final NotificationGuts v = mNotificationGutsExposed; 807 mNotificationGutsExposed = null; 808 809 if (v.getWindowToken() == null) return; 810 811 final int x = (v.getLeft() + v.getRight()) / 2; 812 final int y = (v.getTop() + v.getActualHeight() / 2); 813 final Animator a = ViewAnimationUtils.createCircularReveal(v, 814 x, y, x, 0); 815 a.setDuration(200); 816 a.setInterpolator(mFastOutLinearIn); 817 a.addListener(new AnimatorListenerAdapter() { 818 @Override 819 public void onAnimationEnd(Animator animation) { 820 super.onAnimationEnd(animation); 821 v.setVisibility(View.GONE); 822 } 823 }); 824 a.start(); 825 } 826 } 827 828 public void onHeadsUpDismissed() { 829 } 830 831 @Override 832 public void showRecentApps(boolean triggeredFromAltTab) { 833 int msg = MSG_SHOW_RECENT_APPS; 834 mHandler.removeMessages(msg); 835 mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 0).sendToTarget(); 836 } 837 838 @Override 839 public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 840 int msg = MSG_HIDE_RECENT_APPS; 841 mHandler.removeMessages(msg); 842 mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 843 triggeredFromHomeKey ? 1 : 0).sendToTarget(); 844 } 845 846 @Override 847 public void toggleRecentApps() { 848 int msg = MSG_TOGGLE_RECENTS_APPS; 849 mHandler.removeMessages(msg); 850 mHandler.sendEmptyMessage(msg); 851 } 852 853 @Override 854 public void preloadRecentApps() { 855 int msg = MSG_PRELOAD_RECENT_APPS; 856 mHandler.removeMessages(msg); 857 mHandler.sendEmptyMessage(msg); 858 } 859 860 @Override 861 public void cancelPreloadRecentApps() { 862 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 863 mHandler.removeMessages(msg); 864 mHandler.sendEmptyMessage(msg); 865 } 866 867 /** Jumps to the next affiliated task in the group. */ 868 public void showNextAffiliatedTask() { 869 int msg = MSG_SHOW_NEXT_AFFILIATED_TASK; 870 mHandler.removeMessages(msg); 871 mHandler.sendEmptyMessage(msg); 872 } 873 874 /** Jumps to the previous affiliated task in the group. */ 875 public void showPreviousAffiliatedTask() { 876 int msg = MSG_SHOW_PREV_AFFILIATED_TASK; 877 mHandler.removeMessages(msg); 878 mHandler.sendEmptyMessage(msg); 879 } 880 881 @Override 882 public void showSearchPanel() { 883 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 884 mSearchPanelView.show(true, true); 885 } 886 } 887 888 @Override 889 public void hideSearchPanel() { 890 int msg = MSG_CLOSE_SEARCH_PANEL; 891 mHandler.removeMessages(msg); 892 mHandler.sendEmptyMessage(msg); 893 } 894 895 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 896 LayoutParams layoutParams); 897 898 protected void updateSearchPanel() { 899 // Search Panel 900 boolean visible = false; 901 if (mSearchPanelView != null) { 902 visible = mSearchPanelView.isShowing(); 903 mWindowManager.removeView(mSearchPanelView); 904 } 905 906 // Provide SearchPanel with a temporary parent to allow layout params to work. 907 LinearLayout tmpRoot = new LinearLayout(mContext); 908 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 909 R.layout.status_bar_search_panel, tmpRoot, false); 910 mSearchPanelView.setOnTouchListener( 911 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 912 mSearchPanelView.setVisibility(View.GONE); 913 914 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 915 916 mWindowManager.addView(mSearchPanelView, lp); 917 mSearchPanelView.setBar(this); 918 if (visible) { 919 mSearchPanelView.show(true, false); 920 } 921 } 922 923 protected H createHandler() { 924 return new H(); 925 } 926 927 static void sendCloseSystemWindows(Context context, String reason) { 928 if (ActivityManagerNative.isSystemReady()) { 929 try { 930 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 931 } catch (RemoteException e) { 932 } 933 } 934 } 935 936 protected abstract View getStatusBarView(); 937 938 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 939 // additional optimization when we have software system buttons - start loading the recent 940 // tasks on touch down 941 @Override 942 public boolean onTouch(View v, MotionEvent event) { 943 int action = event.getAction() & MotionEvent.ACTION_MASK; 944 if (action == MotionEvent.ACTION_DOWN) { 945 preloadRecents(); 946 } else if (action == MotionEvent.ACTION_CANCEL) { 947 cancelPreloadingRecents(); 948 } else if (action == MotionEvent.ACTION_UP) { 949 if (!v.isPressed()) { 950 cancelPreloadingRecents(); 951 } 952 953 } 954 return false; 955 } 956 }; 957 958 /** Proxy for RecentsComponent */ 959 960 protected void showRecents(boolean triggeredFromAltTab) { 961 if (mRecents != null) { 962 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 963 mRecents.showRecents(triggeredFromAltTab, getStatusBarView()); 964 } 965 } 966 967 protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 968 if (mRecents != null) { 969 mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey); 970 } 971 } 972 973 protected void toggleRecents() { 974 if (mRecents != null) { 975 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 976 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView()); 977 } 978 } 979 980 protected void preloadRecents() { 981 if (mRecents != null) { 982 mRecents.preloadRecents(); 983 } 984 } 985 986 protected void cancelPreloadingRecents() { 987 if (mRecents != null) { 988 mRecents.cancelPreloadingRecents(); 989 } 990 } 991 992 protected void showRecentsNextAffiliatedTask() { 993 if (mRecents != null) { 994 mRecents.showNextAffiliatedTask(); 995 } 996 } 997 998 protected void showRecentsPreviousAffiliatedTask() { 999 if (mRecents != null) { 1000 mRecents.showPrevAffiliatedTask(); 1001 } 1002 } 1003 1004 @Override 1005 public void onVisibilityChanged(boolean visible) { 1006 // Do nothing 1007 } 1008 1009 public abstract void resetHeadsUpDecayTimer(); 1010 1011 public abstract void scheduleHeadsUpOpen(); 1012 1013 public abstract void scheduleHeadsUpClose(); 1014 1015 public abstract void scheduleHeadsUpEscalation(); 1016 1017 /** 1018 * Save the current "public" (locked and secure) state of the lockscreen. 1019 */ 1020 public void setLockscreenPublicMode(boolean publicMode) { 1021 mLockscreenPublicMode = publicMode; 1022 } 1023 1024 public boolean isLockscreenPublicMode() { 1025 return mLockscreenPublicMode; 1026 } 1027 1028 /** 1029 * Has the given user chosen to allow their private (full) notifications to be shown even 1030 * when the lockscreen is in "public" (secure & locked) mode? 1031 */ 1032 public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { 1033 if (userHandle == UserHandle.USER_ALL) { 1034 return true; 1035 } 1036 1037 if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { 1038 final boolean allowed = 0 != Settings.Secure.getIntForUser( 1039 mContext.getContentResolver(), 1040 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); 1041 final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, 1042 userHandle); 1043 final boolean allowedByDpm = (dpmFlags 1044 & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; 1045 mUsersAllowingPrivateNotifications.append(userHandle, allowed && allowedByDpm); 1046 return allowed; 1047 } 1048 1049 return mUsersAllowingPrivateNotifications.get(userHandle); 1050 } 1051 1052 /** 1053 * Returns true if we're on a secure lockscreen and the user wants to hide "sensitive" 1054 * notification data. If so, private notifications should show their (possibly 1055 * auto-generated) publicVersion, and secret notifications should be totally invisible. 1056 */ 1057 @Override // NotificationData.Environment 1058 public boolean shouldHideSensitiveContents(int userid) { 1059 return isLockscreenPublicMode() && !userAllowsPrivateNotificationsInPublic(userid); 1060 } 1061 1062 public void onNotificationClear(StatusBarNotification notification) { 1063 try { 1064 mBarService.onNotificationClear( 1065 notification.getPackageName(), 1066 notification.getTag(), 1067 notification.getId(), 1068 notification.getUserId()); 1069 } catch (android.os.RemoteException ex) { 1070 // oh well 1071 } 1072 } 1073 1074 protected class H extends Handler { 1075 public void handleMessage(Message m) { 1076 switch (m.what) { 1077 case MSG_SHOW_RECENT_APPS: 1078 showRecents(m.arg1 > 0); 1079 break; 1080 case MSG_HIDE_RECENT_APPS: 1081 hideRecents(m.arg1 > 0, m.arg2 > 0); 1082 break; 1083 case MSG_TOGGLE_RECENTS_APPS: 1084 toggleRecents(); 1085 break; 1086 case MSG_PRELOAD_RECENT_APPS: 1087 preloadRecents(); 1088 break; 1089 case MSG_CANCEL_PRELOAD_RECENT_APPS: 1090 cancelPreloadingRecents(); 1091 break; 1092 case MSG_SHOW_NEXT_AFFILIATED_TASK: 1093 showRecentsNextAffiliatedTask(); 1094 break; 1095 case MSG_SHOW_PREV_AFFILIATED_TASK: 1096 showRecentsPreviousAffiliatedTask(); 1097 break; 1098 case MSG_CLOSE_SEARCH_PANEL: 1099 if (DEBUG) Log.d(TAG, "closing search panel"); 1100 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 1101 mSearchPanelView.show(false, true); 1102 } 1103 break; 1104 } 1105 } 1106 } 1107 1108 public class TouchOutsideListener implements View.OnTouchListener { 1109 private int mMsg; 1110 private StatusBarPanel mPanel; 1111 1112 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1113 mMsg = msg; 1114 mPanel = panel; 1115 } 1116 1117 public boolean onTouch(View v, MotionEvent ev) { 1118 final int action = ev.getAction(); 1119 if (action == MotionEvent.ACTION_OUTSIDE 1120 || (action == MotionEvent.ACTION_DOWN 1121 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1122 mHandler.removeMessages(mMsg); 1123 mHandler.sendEmptyMessage(mMsg); 1124 return true; 1125 } 1126 return false; 1127 } 1128 } 1129 1130 protected void workAroundBadLayerDrawableOpacity(View v) { 1131 } 1132 1133 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 1134 return inflateViews(entry, parent, false); 1135 } 1136 1137 protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { 1138 return inflateViews(entry, parent, true); 1139 } 1140 1141 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { 1142 PackageManager pmUser = getPackageManagerForUser( 1143 entry.notification.getUser().getIdentifier()); 1144 1145 int maxHeight = mRowMaxHeight; 1146 final StatusBarNotification sbn = entry.notification; 1147 RemoteViews contentView = sbn.getNotification().contentView; 1148 RemoteViews bigContentView = sbn.getNotification().bigContentView; 1149 1150 if (isHeadsUp) { 1151 maxHeight = 1152 mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height); 1153 bigContentView = sbn.getNotification().headsUpContentView; 1154 } 1155 1156 if (contentView == null) { 1157 return false; 1158 } 1159 1160 if (DEBUG) { 1161 Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion); 1162 } 1163 1164 Notification publicNotification = sbn.getNotification().publicVersion; 1165 1166 ExpandableNotificationRow row; 1167 1168 // Stash away previous user expansion state so we can restore it at 1169 // the end. 1170 boolean hasUserChangedExpansion = false; 1171 boolean userExpanded = false; 1172 boolean userLocked = false; 1173 1174 if (entry.row != null) { 1175 row = entry.row; 1176 hasUserChangedExpansion = row.hasUserChangedExpansion(); 1177 userExpanded = row.isUserExpanded(); 1178 userLocked = row.isUserLocked(); 1179 entry.reset(); 1180 if (hasUserChangedExpansion) { 1181 row.setUserExpanded(userExpanded); 1182 } 1183 } else { 1184 // create the row view 1185 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( 1186 Context.LAYOUT_INFLATER_SERVICE); 1187 row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row, 1188 parent, false); 1189 row.setExpansionLogger(this, entry.notification.getKey()); 1190 } 1191 1192 // the notification inspector (see SwipeHelper.setLongPressListener) 1193 row.setTag(sbn.getPackageName()); 1194 final View guts = row.findViewById(R.id.notification_guts); 1195 final String pkg = entry.notification.getPackageName(); 1196 String appname = pkg; 1197 Drawable pkgicon = null; 1198 int appUid = -1; 1199 try { 1200 final ApplicationInfo info = pmUser.getApplicationInfo(pkg, 1201 PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS); 1202 if (info != null) { 1203 appname = String.valueOf(pmUser.getApplicationLabel(info)); 1204 pkgicon = pmUser.getApplicationIcon(info); 1205 appUid = info.uid; 1206 } 1207 } catch (NameNotFoundException e) { 1208 // app is gone, just show package name and generic icon 1209 pkgicon = pmUser.getDefaultActivityIcon(); 1210 } 1211 ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon); 1212 ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(entry.notification.getPostTime()); 1213 ((TextView) row.findViewById(R.id.pkgname)).setText(appname); 1214 final View settingsButton = guts.findViewById(R.id.notification_inspect_item); 1215 final View appSettingsButton 1216 = guts.findViewById(R.id.notification_inspect_app_provided_settings); 1217 if (appUid >= 0) { 1218 final int appUidF = appUid; 1219 settingsButton.setOnClickListener(new View.OnClickListener() { 1220 public void onClick(View v) { 1221 startAppNotificationSettingsActivity(pkg, appUidF); 1222 } 1223 }); 1224 1225 final Intent appSettingsQueryIntent 1226 = new Intent(Intent.ACTION_MAIN) 1227 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) 1228 .setPackage(pkg); 1229 List<ResolveInfo> infos = pmUser.queryIntentActivities(appSettingsQueryIntent, 0); 1230 if (infos.size() > 0) { 1231 appSettingsButton.setVisibility(View.VISIBLE); 1232 appSettingsButton.setContentDescription( 1233 mContext.getResources().getString( 1234 R.string.status_bar_notification_app_settings_title, 1235 appname 1236 )); 1237 final Intent appSettingsLaunchIntent = new Intent(appSettingsQueryIntent) 1238 .setClassName(pkg, infos.get(0).activityInfo.name); 1239 appSettingsButton.setOnClickListener(new View.OnClickListener() { 1240 public void onClick(View v) { 1241 startAppOwnNotificationSettingsActivity(appSettingsLaunchIntent, 1242 sbn.getId(), 1243 sbn.getTag(), 1244 appUidF); 1245 } 1246 }); 1247 } 1248 } else { 1249 settingsButton.setVisibility(View.GONE); 1250 appSettingsButton.setVisibility(View.GONE); 1251 } 1252 1253 workAroundBadLayerDrawableOpacity(row); 1254 View vetoButton = updateNotificationVetoButton(row, sbn); 1255 vetoButton.setContentDescription(mContext.getString( 1256 R.string.accessibility_remove_notification)); 1257 1258 // NB: the large icon is now handled entirely by the template 1259 1260 // bind the click event to the content area 1261 NotificationContentView expanded = 1262 (NotificationContentView) row.findViewById(R.id.expanded); 1263 NotificationContentView expandedPublic = 1264 (NotificationContentView) row.findViewById(R.id.expandedPublic); 1265 1266 row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1267 1268 PendingIntent contentIntent = sbn.getNotification().contentIntent; 1269 if (contentIntent != null) { 1270 final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(), 1271 isHeadsUp); 1272 row.setOnClickListener(listener); 1273 } else { 1274 row.setOnClickListener(null); 1275 } 1276 1277 // set up the adaptive layout 1278 View contentViewLocal = null; 1279 View bigContentViewLocal = null; 1280 try { 1281 contentViewLocal = contentView.apply(mContext, expanded, 1282 mOnClickHandler); 1283 if (bigContentView != null) { 1284 bigContentViewLocal = bigContentView.apply(mContext, expanded, 1285 mOnClickHandler); 1286 } 1287 } 1288 catch (RuntimeException e) { 1289 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 1290 Log.e(TAG, "couldn't inflate view for notification " + ident, e); 1291 return false; 1292 } 1293 1294 if (contentViewLocal != null) { 1295 contentViewLocal.setIsRootNamespace(true); 1296 expanded.setContractedChild(contentViewLocal); 1297 } 1298 if (bigContentViewLocal != null) { 1299 bigContentViewLocal.setIsRootNamespace(true); 1300 expanded.setExpandedChild(bigContentViewLocal); 1301 } 1302 1303 // now the public version 1304 View publicViewLocal = null; 1305 if (publicNotification != null) { 1306 try { 1307 publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic, 1308 mOnClickHandler); 1309 1310 if (publicViewLocal != null) { 1311 publicViewLocal.setIsRootNamespace(true); 1312 expandedPublic.setContractedChild(publicViewLocal); 1313 } 1314 } 1315 catch (RuntimeException e) { 1316 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 1317 Log.e(TAG, "couldn't inflate public view for notification " + ident, e); 1318 publicViewLocal = null; 1319 } 1320 } 1321 1322 if (publicViewLocal == null) { 1323 // Add a basic notification template 1324 publicViewLocal = LayoutInflater.from(mContext).inflate( 1325 com.android.internal.R.layout.notification_template_material_base, 1326 expandedPublic, false); 1327 publicViewLocal.setIsRootNamespace(true); 1328 expandedPublic.setContractedChild(publicViewLocal); 1329 1330 final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title); 1331 try { 1332 title.setText(pmUser.getApplicationLabel( 1333 pmUser.getApplicationInfo(entry.notification.getPackageName(), 0))); 1334 } catch (NameNotFoundException e) { 1335 title.setText(entry.notification.getPackageName()); 1336 } 1337 1338 final ImageView icon = (ImageView) publicViewLocal.findViewById( 1339 com.android.internal.R.id.icon); 1340 final ImageView profileBadge = (ImageView) publicViewLocal.findViewById( 1341 com.android.internal.R.id.profile_badge_line3); 1342 1343 final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(), 1344 entry.notification.getUser(), 1345 entry.notification.getNotification().icon, 1346 entry.notification.getNotification().iconLevel, 1347 entry.notification.getNotification().number, 1348 entry.notification.getNotification().tickerText); 1349 1350 Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic); 1351 icon.setImageDrawable(iconDrawable); 1352 if (mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) { 1353 icon.setBackgroundResource( 1354 com.android.internal.R.drawable.notification_icon_legacy_bg); 1355 int padding = mContext.getResources().getDimensionPixelSize( 1356 com.android.internal.R.dimen.notification_large_icon_circle_padding); 1357 icon.setPadding(padding, padding, padding, padding); 1358 } 1359 1360 if (profileBadge != null) { 1361 Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity( 1362 entry.notification.getUser(), 0); 1363 if (profileDrawable != null) { 1364 profileBadge.setImageDrawable(profileDrawable); 1365 profileBadge.setVisibility(View.VISIBLE); 1366 } else { 1367 profileBadge.setVisibility(View.GONE); 1368 } 1369 } 1370 1371 final View privateTime = contentViewLocal.findViewById(com.android.internal.R.id.time); 1372 if (privateTime != null && privateTime.getVisibility() == View.VISIBLE) { 1373 final View timeStub = publicViewLocal.findViewById(com.android.internal.R.id.time); 1374 timeStub.setVisibility(View.VISIBLE); 1375 final DateTimeView dateTimeView = (DateTimeView) 1376 publicViewLocal.findViewById(com.android.internal.R.id.time); 1377 dateTimeView.setTime(entry.notification.getNotification().when); 1378 } 1379 1380 final TextView text = (TextView) publicViewLocal.findViewById( 1381 com.android.internal.R.id.text); 1382 if (text != null) { 1383 text.setText(R.string.notification_hidden_text); 1384 text.setTextAppearance(mContext, 1385 R.style.TextAppearance_Material_Notification_Parenthetical); 1386 } 1387 1388 int topPadding = Notification.Builder.calculateTopPadding(mContext, 1389 false /* hasThreeLines */, 1390 mContext.getResources().getConfiguration().fontScale); 1391 title.setPadding(0, topPadding, 0, 0); 1392 1393 entry.autoRedacted = true; 1394 } 1395 1396 row.setClearable(sbn.isClearable()); 1397 1398 if (MULTIUSER_DEBUG) { 1399 TextView debug = (TextView) row.findViewById(R.id.debug_info); 1400 if (debug != null) { 1401 debug.setVisibility(View.VISIBLE); 1402 debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId()); 1403 } 1404 } 1405 entry.row = row; 1406 entry.row.setHeightRange(mRowMinHeight, maxHeight); 1407 entry.row.setOnActivatedListener(this); 1408 entry.expanded = contentViewLocal; 1409 entry.expandedPublic = publicViewLocal; 1410 entry.setBigContentView(bigContentViewLocal); 1411 1412 applyColorsAndBackgrounds(sbn, entry); 1413 1414 // Restore previous flags. 1415 if (hasUserChangedExpansion) { 1416 // Note: setUserExpanded() conveniently ignores calls with 1417 // userExpanded=true if !isExpandable(). 1418 row.setUserExpanded(userExpanded); 1419 } 1420 row.setUserLocked(userLocked); 1421 1422 return true; 1423 } 1424 1425 public NotificationClicker makeClicker(PendingIntent intent, String notificationKey, 1426 boolean forHun) { 1427 return new NotificationClicker(intent, notificationKey, forHun); 1428 } 1429 1430 protected class NotificationClicker implements View.OnClickListener { 1431 private PendingIntent mIntent; 1432 private final String mNotificationKey; 1433 private boolean mIsHeadsUp; 1434 1435 public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) { 1436 mIntent = intent; 1437 mNotificationKey = notificationKey; 1438 mIsHeadsUp = forHun; 1439 } 1440 1441 public void onClick(final View v) { 1442 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 1443 dismissKeyguardThenExecute(new OnDismissAction() { 1444 public boolean onDismiss() { 1445 if (mIsHeadsUp) { 1446 mHeadsUpNotificationView.clear(); 1447 } 1448 AsyncTask.execute(new Runnable() { 1449 @Override 1450 public void run() { 1451 if (keyguardShowing) { 1452 try { 1453 ActivityManagerNative.getDefault() 1454 .keyguardWaitingForActivityDrawn(); 1455 // The intent we are sending is for the application, which 1456 // won't have permission to immediately start an activity after 1457 // the user switches to home. We know it is safe to do at this 1458 // point, so make sure new activity switches are now allowed. 1459 ActivityManagerNative.getDefault().resumeAppSwitches(); 1460 } catch (RemoteException e) { 1461 } 1462 } 1463 1464 if (mIntent != null) { 1465 try { 1466 mIntent.send(); 1467 } catch (PendingIntent.CanceledException e) { 1468 // the stack trace isn't very helpful here. 1469 // Just log the exception message. 1470 Log.w(TAG, "Sending contentIntent failed: " + e); 1471 1472 // TODO: Dismiss Keyguard. 1473 } 1474 if (mIntent.isActivity()) { 1475 overrideActivityPendingAppTransition(keyguardShowing); 1476 } 1477 } 1478 1479 try { 1480 mBarService.onNotificationClick(mNotificationKey); 1481 } catch (RemoteException ex) { 1482 // system process is dead if we're here. 1483 } 1484 } 1485 }); 1486 1487 // close the shade if it was open 1488 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 1489 visibilityChanged(false); 1490 1491 return mIntent != null && mIntent.isActivity(); 1492 } 1493 }, false /* afterKeyguardGone */); 1494 } 1495 } 1496 1497 public void animateCollapsePanels(int flags, boolean force) { 1498 } 1499 1500 public void overrideActivityPendingAppTransition(boolean keyguardShowing) { 1501 if (keyguardShowing) { 1502 try { 1503 mWindowManagerService.overridePendingAppTransition(null, 0, 0, null); 1504 } catch (RemoteException e) { 1505 Log.w(TAG, "Error overriding app transition: " + e); 1506 } 1507 } 1508 } 1509 1510 /** 1511 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 1512 * This was added last-minute and is inconsistent with the way the rest of the notifications 1513 * are handled, because the notification isn't really cancelled. The lights are just 1514 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1515 * this is what he wants. (see bug 1131461) 1516 */ 1517 protected void visibilityChanged(boolean visible) { 1518 if (mPanelSlightlyVisible != visible) { 1519 mPanelSlightlyVisible = visible; 1520 if (!visible) { 1521 dismissPopups(); 1522 } 1523 try { 1524 if (visible) { 1525 mBarService.onPanelRevealed(); 1526 } else { 1527 mBarService.onPanelHidden(); 1528 } 1529 } catch (RemoteException ex) { 1530 // Won't fail unless the world has ended. 1531 } 1532 } 1533 } 1534 1535 /** 1536 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 1537 * about the failure. 1538 * 1539 * WARNING: this will call back into us. Don't hold any locks. 1540 */ 1541 void handleNotificationError(StatusBarNotification n, String message) { 1542 removeNotification(n.getKey(), null); 1543 try { 1544 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), 1545 n.getInitialPid(), message, n.getUserId()); 1546 } catch (RemoteException ex) { 1547 // The end is nigh. 1548 } 1549 } 1550 1551 protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { 1552 NotificationData.Entry entry = mNotificationData.remove(key, ranking); 1553 if (entry == null) { 1554 Log.w(TAG, "removeNotification for unknown key: " + key); 1555 return null; 1556 } 1557 updateNotifications(); 1558 return entry.notification; 1559 } 1560 1561 protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) { 1562 if (DEBUG) { 1563 Log.d(TAG, "createNotificationViews(notification=" + sbn); 1564 } 1565 // Construct the icon. 1566 Notification n = sbn.getNotification(); 1567 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1568 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n); 1569 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1570 1571 final StatusBarIcon ic = new StatusBarIcon(sbn.getPackageName(), 1572 sbn.getUser(), 1573 n.icon, 1574 n.iconLevel, 1575 n.number, 1576 n.tickerText); 1577 if (!iconView.set(ic)) { 1578 handleNotificationError(sbn, "Couldn't create icon: " + ic); 1579 return null; 1580 } 1581 // Construct the expanded view. 1582 NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView); 1583 if (!inflateViews(entry, mStackScroller)) { 1584 handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn); 1585 return null; 1586 } 1587 return entry; 1588 } 1589 1590 protected void addNotificationViews(Entry entry, RankingMap ranking) { 1591 if (entry == null) { 1592 return; 1593 } 1594 // Add the expanded view and icon. 1595 mNotificationData.add(entry, ranking); 1596 updateNotifications(); 1597 } 1598 1599 /** 1600 * @return The number of notifications we show on Keyguard. 1601 */ 1602 protected abstract int getMaxKeyguardNotifications(); 1603 1604 /** 1605 * Updates expanded, dimmed and locked states of notification rows. 1606 */ 1607 protected void updateRowStates() { 1608 int maxKeyguardNotifications = getMaxKeyguardNotifications(); 1609 mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); 1610 1611 ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); 1612 final int N = activeNotifications.size(); 1613 1614 int visibleNotifications = 0; 1615 boolean onKeyguard = mState == StatusBarState.KEYGUARD; 1616 for (int i = 0; i < N; i++) { 1617 NotificationData.Entry entry = activeNotifications.get(i); 1618 if (onKeyguard) { 1619 entry.row.setExpansionDisabled(true); 1620 } else { 1621 entry.row.setExpansionDisabled(false); 1622 if (!entry.row.isUserLocked()) { 1623 boolean top = (i == 0); 1624 entry.row.setSystemExpanded(top); 1625 } 1626 } 1627 boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); 1628 if ((isLockscreenPublicMode() && !showOnKeyguard) || 1629 (onKeyguard && (visibleNotifications >= maxKeyguardNotifications 1630 || !showOnKeyguard))) { 1631 entry.row.setVisibility(View.GONE); 1632 if (onKeyguard && showOnKeyguard) { 1633 mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); 1634 } 1635 } else { 1636 boolean wasGone = entry.row.getVisibility() == View.GONE; 1637 entry.row.setVisibility(View.VISIBLE); 1638 if (wasGone) { 1639 // notify the scroller of a child addition 1640 mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */); 1641 } 1642 visibleNotifications++; 1643 } 1644 } 1645 1646 if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { 1647 mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE); 1648 } else { 1649 mKeyguardIconOverflowContainer.setVisibility(View.GONE); 1650 } 1651 1652 mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, 1653 mStackScroller.getChildCount() - 3); 1654 mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2); 1655 mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1); 1656 } 1657 1658 private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { 1659 return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey()); 1660 } 1661 1662 protected void setZenMode(int mode) { 1663 if (!isDeviceProvisioned()) return; 1664 mZenMode = mode; 1665 updateNotifications(); 1666 } 1667 1668 // extended in PhoneStatusBar 1669 protected void setShowLockscreenNotifications(boolean show) { 1670 mShowLockscreenNotifications = show; 1671 } 1672 1673 private void updateLockscreenNotificationSetting() { 1674 final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(), 1675 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1676 1, 1677 mCurrentUserId) != 0; 1678 final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( 1679 null /* admin */, mCurrentUserId); 1680 final boolean allowedByDpm = (dpmFlags 1681 & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; 1682 setShowLockscreenNotifications(show && allowedByDpm); 1683 } 1684 1685 protected abstract void haltTicker(); 1686 protected abstract void setAreThereNotifications(); 1687 protected abstract void updateNotifications(); 1688 protected abstract void tick(StatusBarNotification n, boolean firstTime); 1689 protected abstract void updateExpandedViewPos(int expandedPosition); 1690 protected abstract boolean shouldDisableNavbarGestures(); 1691 1692 public abstract void addNotification(StatusBarNotification notification, 1693 RankingMap ranking); 1694 protected abstract void updateNotificationRanking(RankingMap ranking); 1695 public abstract void removeNotification(String key, RankingMap ranking); 1696 1697 public void updateNotification(StatusBarNotification notification, RankingMap ranking) { 1698 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); 1699 1700 final String key = notification.getKey(); 1701 boolean wasHeadsUp = isHeadsUp(key); 1702 Entry oldEntry; 1703 if (wasHeadsUp) { 1704 oldEntry = mHeadsUpNotificationView.getEntry(); 1705 } else { 1706 oldEntry = mNotificationData.get(key); 1707 } 1708 if (oldEntry == null) { 1709 return; 1710 } 1711 1712 final StatusBarNotification oldNotification = oldEntry.notification; 1713 1714 // XXX: modify when we do something more intelligent with the two content views 1715 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 1716 Notification n = notification.getNotification(); 1717 final RemoteViews contentView = n.contentView; 1718 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 1719 final RemoteViews bigContentView = n.bigContentView; 1720 final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView; 1721 final RemoteViews headsUpContentView = n.headsUpContentView; 1722 final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; 1723 final RemoteViews oldPublicContentView = oldPublicNotification != null 1724 ? oldPublicNotification.contentView : null; 1725 final Notification publicNotification = n.publicVersion; 1726 final RemoteViews publicContentView = publicNotification != null 1727 ? publicNotification.contentView : null; 1728 1729 if (DEBUG) { 1730 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 1731 + " ongoing=" + oldNotification.isOngoing() 1732 + " expanded=" + oldEntry.expanded 1733 + " contentView=" + oldContentView 1734 + " bigContentView=" + oldBigContentView 1735 + " publicView=" + oldPublicContentView 1736 + " rowParent=" + oldEntry.row.getParent()); 1737 Log.d(TAG, "new notification: when=" + n.when 1738 + " ongoing=" + oldNotification.isOngoing() 1739 + " contentView=" + contentView 1740 + " bigContentView=" + bigContentView 1741 + " publicView=" + publicContentView); 1742 } 1743 1744 // Can we just reapply the RemoteViews in place? 1745 1746 // 1U is never null 1747 boolean contentsUnchanged = oldEntry.expanded != null 1748 && contentView.getPackage() != null 1749 && oldContentView.getPackage() != null 1750 && oldContentView.getPackage().equals(contentView.getPackage()) 1751 && oldContentView.getLayoutId() == contentView.getLayoutId(); 1752 // large view may be null 1753 boolean bigContentsUnchanged = 1754 (oldEntry.getBigContentView() == null && bigContentView == null) 1755 || ((oldEntry.getBigContentView() != null && bigContentView != null) 1756 && bigContentView.getPackage() != null 1757 && oldBigContentView.getPackage() != null 1758 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 1759 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 1760 boolean headsUpContentsUnchanged = 1761 (oldHeadsUpContentView == null && headsUpContentView == null) 1762 || ((oldHeadsUpContentView != null && headsUpContentView != null) 1763 && headsUpContentView.getPackage() != null 1764 && oldHeadsUpContentView.getPackage() != null 1765 && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) 1766 && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); 1767 boolean publicUnchanged = 1768 (oldPublicContentView == null && publicContentView == null) 1769 || ((oldPublicContentView != null && publicContentView != null) 1770 && publicContentView.getPackage() != null 1771 && oldPublicContentView.getPackage() != null 1772 && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) 1773 && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); 1774 boolean updateTicker = n.tickerText != null 1775 && !TextUtils.equals(n.tickerText, 1776 oldEntry.notification.getNotification().tickerText); 1777 1778 final boolean shouldInterrupt = shouldInterrupt(notification); 1779 final boolean alertAgain = alertAgain(oldEntry); 1780 boolean updateSuccessful = false; 1781 if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged 1782 && publicUnchanged) { 1783 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); 1784 oldEntry.notification = notification; 1785 try { 1786 if (oldEntry.icon != null) { 1787 // Update the icon 1788 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1789 notification.getUser(), 1790 n.icon, 1791 n.iconLevel, 1792 n.number, 1793 n.tickerText); 1794 oldEntry.icon.setNotification(n); 1795 if (!oldEntry.icon.set(ic)) { 1796 handleNotificationError(notification, "Couldn't update icon: " + ic); 1797 return; 1798 } 1799 } 1800 1801 if (wasHeadsUp) { 1802 if (shouldInterrupt) { 1803 updateHeadsUpViews(oldEntry, notification); 1804 if (alertAgain) { 1805 resetHeadsUpDecayTimer(); 1806 } 1807 } else { 1808 // we updated the notification above, so release to build a new shade entry 1809 mHeadsUpNotificationView.releaseAndClose(); 1810 return; 1811 } 1812 } else { 1813 if (shouldInterrupt && alertAgain) { 1814 removeNotificationViews(key, ranking); 1815 addNotification(notification, ranking); //this will pop the headsup 1816 } else { 1817 updateNotificationViews(oldEntry, notification); 1818 } 1819 } 1820 mNotificationData.updateRanking(ranking); 1821 updateNotifications(); 1822 updateSuccessful = true; 1823 } 1824 catch (RuntimeException e) { 1825 // It failed to add cleanly. Log, and remove the view from the panel. 1826 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1827 } 1828 } 1829 if (!updateSuccessful) { 1830 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); 1831 if (wasHeadsUp) { 1832 if (shouldInterrupt) { 1833 if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key); 1834 Entry newEntry = new Entry(notification, null); 1835 ViewGroup holder = mHeadsUpNotificationView.getHolder(); 1836 if (inflateViewsForHeadsUp(newEntry, holder)) { 1837 mHeadsUpNotificationView.showNotification(newEntry); 1838 if (alertAgain) { 1839 resetHeadsUpDecayTimer(); 1840 } 1841 } else { 1842 Log.w(TAG, "Couldn't create new updated headsup for package " 1843 + contentView.getPackage()); 1844 } 1845 } else { 1846 if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key); 1847 oldEntry.notification = notification; 1848 mHeadsUpNotificationView.releaseAndClose(); 1849 return; 1850 } 1851 } else { 1852 if (shouldInterrupt && alertAgain) { 1853 if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key); 1854 removeNotificationViews(key, ranking); 1855 addNotification(notification, ranking); //this will pop the headsup 1856 } else { 1857 if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key); 1858 oldEntry.notification = notification; 1859 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1860 notification.getUser(), 1861 n.icon, 1862 n.iconLevel, 1863 n.number, 1864 n.tickerText); 1865 oldEntry.icon.setNotification(n); 1866 oldEntry.icon.set(ic); 1867 inflateViews(oldEntry, mStackScroller, wasHeadsUp); 1868 mNotificationData.updateRanking(ranking); 1869 updateNotifications(); 1870 } 1871 } 1872 } 1873 1874 // Update the veto button accordingly (and as a result, whether this row is 1875 // swipe-dismissable) 1876 updateNotificationVetoButton(oldEntry.row, notification); 1877 1878 // Is this for you? 1879 boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); 1880 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1881 1882 // Restart the ticker if it's still running 1883 if (updateTicker && isForCurrentUser) { 1884 haltTicker(); 1885 tick(notification, false); 1886 } 1887 1888 // Recalculate the position of the sliding windows and the titles. 1889 setAreThereNotifications(); 1890 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1891 } 1892 1893 private void updateNotificationViews(NotificationData.Entry entry, 1894 StatusBarNotification notification) { 1895 updateNotificationViews(entry, notification, false); 1896 } 1897 1898 private void updateHeadsUpViews(NotificationData.Entry entry, 1899 StatusBarNotification notification) { 1900 updateNotificationViews(entry, notification, true); 1901 } 1902 1903 private void updateNotificationViews(NotificationData.Entry entry, 1904 StatusBarNotification notification, boolean isHeadsUp) { 1905 final RemoteViews contentView = notification.getNotification().contentView; 1906 final RemoteViews bigContentView = isHeadsUp 1907 ? notification.getNotification().headsUpContentView 1908 : notification.getNotification().bigContentView; 1909 final Notification publicVersion = notification.getNotification().publicVersion; 1910 final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView 1911 : null; 1912 1913 // Reapply the RemoteViews 1914 if (entry.row != null) { 1915 entry.row.resetHeight(); 1916 } 1917 contentView.reapply(mContext, entry.expanded, mOnClickHandler); 1918 if (bigContentView != null && entry.getBigContentView() != null) { 1919 bigContentView.reapply(mContext, entry.getBigContentView(), 1920 mOnClickHandler); 1921 } 1922 if (publicContentView != null && entry.getPublicContentView() != null) { 1923 publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); 1924 } 1925 // update the contentIntent 1926 final PendingIntent contentIntent = notification.getNotification().contentIntent; 1927 if (contentIntent != null) { 1928 final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(), 1929 isHeadsUp); 1930 entry.row.setOnClickListener(listener); 1931 } else { 1932 entry.row.setOnClickListener(null); 1933 } 1934 entry.row.notifyContentUpdated(); 1935 } 1936 1937 protected void notifyHeadsUpScreenOn(boolean screenOn) { 1938 if (!screenOn) { 1939 scheduleHeadsUpEscalation(); 1940 } 1941 } 1942 1943 private boolean alertAgain(Entry entry) { 1944 final StatusBarNotification sbn = entry.notification; 1945 return entry == null || !entry.hasInterrupted() 1946 || (sbn.getNotification().flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; 1947 } 1948 1949 protected boolean shouldInterrupt(StatusBarNotification sbn) { 1950 if (mNotificationData.shouldFilterOut(sbn)) { 1951 if (DEBUG) { 1952 Log.d(TAG, "Skipping HUN check for " + sbn.getKey() + " since it's filtered out."); 1953 } 1954 return false; 1955 } 1956 1957 Notification notification = sbn.getNotification(); 1958 // some predicates to make the boolean logic legible 1959 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 1960 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 1961 || notification.sound != null 1962 || notification.vibrate != null; 1963 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 1964 boolean isFullscreen = notification.fullScreenIntent != null; 1965 boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText); 1966 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 1967 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 1968 1969 final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext); 1970 boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker))) 1971 && isAllowed 1972 && mPowerManager.isScreenOn() 1973 && !keyguard.isShowingAndNotOccluded() 1974 && !keyguard.isInputRestricted(); 1975 try { 1976 interrupt = interrupt && !mDreamManager.isDreaming(); 1977 } catch (RemoteException e) { 1978 Log.d(TAG, "failed to query dream manager", e); 1979 } 1980 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 1981 return interrupt; 1982 } 1983 1984 public boolean inKeyguardRestrictedInputMode() { 1985 return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted(); 1986 } 1987 1988 public void setInteracting(int barWindow, boolean interacting) { 1989 // hook for subclasses 1990 } 1991 1992 public void setBouncerShowing(boolean bouncerShowing) { 1993 mBouncerShowing = bouncerShowing; 1994 } 1995 1996 /** 1997 * @return Whether the security bouncer from Keyguard is showing. 1998 */ 1999 public boolean isBouncerShowing() { 2000 return mBouncerShowing; 2001 } 2002 2003 public void destroy() { 2004 if (mSearchPanelView != null) { 2005 mWindowManager.removeViewImmediate(mSearchPanelView); 2006 } 2007 mContext.unregisterReceiver(mBroadcastReceiver); 2008 try { 2009 mNotificationListener.unregisterAsSystemService(); 2010 } catch (RemoteException e) { 2011 // Ignore. 2012 } 2013 } 2014 2015 /** 2016 * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then 2017 * return PackageManager for mContext 2018 */ 2019 protected PackageManager getPackageManagerForUser(int userId) { 2020 Context contextForUser = mContext; 2021 // UserHandle defines special userId as negative values, e.g. USER_ALL 2022 if (userId >= 0) { 2023 try { 2024 // Create a context for the correct user so if a package isn't installed 2025 // for user 0 we can still load information about the package. 2026 contextForUser = 2027 mContext.createPackageContextAsUser(mContext.getPackageName(), 2028 Context.CONTEXT_RESTRICTED, 2029 new UserHandle(userId)); 2030 } catch (NameNotFoundException e) { 2031 // Shouldn't fail to find the package name for system ui. 2032 } 2033 } 2034 return contextForUser.getPackageManager(); 2035 } 2036 2037 @Override 2038 public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { 2039 try { 2040 mBarService.onNotificationExpansionChanged(key, userAction, expanded); 2041 } catch (RemoteException e) { 2042 // Ignore. 2043 } 2044 } 2045} 2046