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