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