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 */ createAndAddWindows()213 protected abstract void createAndAddWindows(); 214 215 protected WindowManager mWindowManager; 216 protected IWindowManager mWindowManagerService; 217 refreshLayout(int layoutDirection)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 isDeviceProvisioned()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 updateCurrentProfilesCache()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 start()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 notifyUserAboutHiddenNotifications()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 userSwitched(int newUserId)665 public void userSwitched(int newUserId) { 666 setHeadsUpUser(newUserId); 667 } 668 setHeadsUpUser(int newUserId)669 private void setHeadsUpUser(int newUserId) { 670 if (mHeadsUpNotificationView != null) { 671 mHeadsUpNotificationView.setUser(newUserId); 672 } 673 } 674 isHeadsUp(String key)675 public boolean isHeadsUp(String key) { 676 return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key); 677 } 678 679 @Override // NotificationData.Environment isNotificationForCurrentProfiles(StatusBarNotification n)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 isCurrentProfile(int userId)690 protected boolean isCurrentProfile(int userId) { 691 synchronized (mCurrentProfiles) { 692 return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; 693 } 694 } 695 696 @Override getCurrentMediaNotificationKey()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 */ dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone)706 protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { 707 action.onDismiss(); 708 } 709 710 @Override onConfigurationChanged(Configuration newConfig)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 updateNotificationVetoButton(View row, StatusBarNotification n)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 applyColorsAndBackgrounds(StatusBarNotification sbn, NotificationData.Entry entry)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 isMediaNotification(NotificationData.Entry entry)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 startAppOwnNotificationSettingsActivity(Intent intent, final int notificationId, final String notificationTag, final int appUid)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 startAppNotificationSettingsActivity(String packageName, final int appUid)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 startNotificationGutsIntent(final Intent intent, final int appUid)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 inflateGuts(ExpandableNotificationRow row)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 getNotificationLongClicker()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 public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { 1532 if (!isDeviceProvisioned()) return; 1533 1534 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 1535 final boolean afterKeyguardGone = intent.isActivity() 1536 && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), 1537 mCurrentUserId); 1538 dismissKeyguardThenExecute(new OnDismissAction() { 1539 public boolean onDismiss() { 1540 new Thread() { 1541 @Override 1542 public void run() { 1543 try { 1544 if (keyguardShowing && !afterKeyguardGone) { 1545 ActivityManagerNative.getDefault() 1546 .keyguardWaitingForActivityDrawn(); 1547 } 1548 1549 // The intent we are sending is for the application, which 1550 // won't have permission to immediately start an activity after 1551 // the user switches to home. We know it is safe to do at this 1552 // point, so make sure new activity switches are now allowed. 1553 ActivityManagerNative.getDefault().resumeAppSwitches(); 1554 } catch (RemoteException e) { 1555 } 1556 1557 try { 1558 intent.send(); 1559 } catch (PendingIntent.CanceledException e) { 1560 // the stack trace isn't very helpful here. 1561 // Just log the exception message. 1562 Log.w(TAG, "Sending intent failed: " + e); 1563 1564 // TODO: Dismiss Keyguard. 1565 } 1566 if (intent.isActivity()) { 1567 overrideActivityPendingAppTransition(keyguardShowing 1568 && !afterKeyguardGone); 1569 } 1570 } 1571 }.start(); 1572 1573 // close the shade if it was open 1574 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, 1575 true /* force */); 1576 visibilityChanged(false); 1577 1578 return true; 1579 } 1580 }, afterKeyguardGone); 1581 } 1582 1583 protected class NotificationClicker implements View.OnClickListener { 1584 private PendingIntent mIntent; 1585 private final String mNotificationKey; 1586 private boolean mIsHeadsUp; 1587 1588 public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) { 1589 mIntent = intent; 1590 mNotificationKey = notificationKey; 1591 mIsHeadsUp = forHun; 1592 } 1593 1594 public void onClick(final View v) { 1595 if (NOTIFICATION_CLICK_DEBUG) { 1596 Log.d(TAG, "Clicked on content of " + mNotificationKey); 1597 } 1598 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 1599 final boolean afterKeyguardGone = mIntent.isActivity() 1600 && PreviewInflater.wouldLaunchResolverActivity(mContext, mIntent.getIntent(), 1601 mCurrentUserId); 1602 dismissKeyguardThenExecute(new OnDismissAction() { 1603 public boolean onDismiss() { 1604 if (mIsHeadsUp) { 1605 // Release the HUN notification to the shade. 1606 // 1607 // In most cases, when FLAG_AUTO_CANCEL is set, the notification will 1608 // become canceled shortly by NoMan, but we can't assume that. 1609 mHeadsUpNotificationView.releaseAndClose(); 1610 } 1611 new Thread() { 1612 @Override 1613 public void run() { 1614 try { 1615 if (keyguardShowing && !afterKeyguardGone) { 1616 ActivityManagerNative.getDefault() 1617 .keyguardWaitingForActivityDrawn(); 1618 } 1619 1620 // The intent we are sending is for the application, which 1621 // won't have permission to immediately start an activity after 1622 // the user switches to home. We know it is safe to do at this 1623 // point, so make sure new activity switches are now allowed. 1624 ActivityManagerNative.getDefault().resumeAppSwitches(); 1625 } catch (RemoteException e) { 1626 } 1627 1628 if (mIntent != null) { 1629 try { 1630 mIntent.send(); 1631 } catch (PendingIntent.CanceledException e) { 1632 // the stack trace isn't very helpful here. 1633 // Just log the exception message. 1634 Log.w(TAG, "Sending contentIntent failed: " + e); 1635 1636 // TODO: Dismiss Keyguard. 1637 } 1638 if (mIntent.isActivity()) { 1639 overrideActivityPendingAppTransition(keyguardShowing 1640 && !afterKeyguardGone); 1641 } 1642 } 1643 1644 try { 1645 mBarService.onNotificationClick(mNotificationKey); 1646 } catch (RemoteException ex) { 1647 // system process is dead if we're here. 1648 } 1649 } 1650 }.start(); 1651 1652 // close the shade if it was open 1653 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, 1654 true /* force */); 1655 visibilityChanged(false); 1656 1657 return mIntent != null && mIntent.isActivity(); 1658 } 1659 }, afterKeyguardGone); 1660 } 1661 } 1662 1663 public void animateCollapsePanels(int flags, boolean force) { 1664 } 1665 1666 public void overrideActivityPendingAppTransition(boolean keyguardShowing) { 1667 if (keyguardShowing) { 1668 try { 1669 mWindowManagerService.overridePendingAppTransition(null, 0, 0, null); 1670 } catch (RemoteException e) { 1671 Log.w(TAG, "Error overriding app transition: " + e); 1672 } 1673 } 1674 } 1675 1676 protected void visibilityChanged(boolean visible) { 1677 if (mVisible != visible) { 1678 mVisible = visible; 1679 if (!visible) { 1680 dismissPopups(); 1681 } 1682 } 1683 updateVisibleToUser(); 1684 } 1685 1686 protected void updateVisibleToUser() { 1687 boolean oldVisibleToUser = mVisibleToUser; 1688 mVisibleToUser = mVisible && mScreenOnFromKeyguard; 1689 1690 if (oldVisibleToUser != mVisibleToUser) { 1691 handleVisibleToUserChanged(mVisibleToUser); 1692 } 1693 } 1694 1695 /** 1696 * The LEDs are turned off when the notification panel is shown, even just a little bit. 1697 * This was added last-minute and is inconsistent with the way the rest of the notifications 1698 * are handled, because the notification isn't really cancelled. The lights are just 1699 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1700 * this is what he wants. (see bug 1131461) 1701 */ 1702 protected void handleVisibleToUserChanged(boolean visibleToUser) { 1703 try { 1704 if (visibleToUser) { 1705 // Only stop blinking, vibrating, ringing when the user went into the shade 1706 // manually (SHADE or SHADE_LOCKED). 1707 boolean clearNotificationEffects = 1708 (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); 1709 mBarService.onPanelRevealed(clearNotificationEffects); 1710 } else { 1711 mBarService.onPanelHidden(); 1712 } 1713 } catch (RemoteException ex) { 1714 // Won't fail unless the world has ended. 1715 } 1716 } 1717 1718 /** 1719 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 1720 * about the failure. 1721 * 1722 * WARNING: this will call back into us. Don't hold any locks. 1723 */ 1724 void handleNotificationError(StatusBarNotification n, String message) { 1725 removeNotification(n.getKey(), null); 1726 try { 1727 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), 1728 n.getInitialPid(), message, n.getUserId()); 1729 } catch (RemoteException ex) { 1730 // The end is nigh. 1731 } 1732 } 1733 1734 protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { 1735 NotificationData.Entry entry = mNotificationData.remove(key, ranking); 1736 if (entry == null) { 1737 Log.w(TAG, "removeNotification for unknown key: " + key); 1738 return null; 1739 } 1740 updateNotifications(); 1741 return entry.notification; 1742 } 1743 1744 protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) { 1745 if (DEBUG) { 1746 Log.d(TAG, "createNotificationViews(notification=" + sbn); 1747 } 1748 // Construct the icon. 1749 Notification n = sbn.getNotification(); 1750 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1751 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n); 1752 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1753 1754 final StatusBarIcon ic = new StatusBarIcon(sbn.getPackageName(), 1755 sbn.getUser(), 1756 n.icon, 1757 n.iconLevel, 1758 n.number, 1759 n.tickerText); 1760 if (!iconView.set(ic)) { 1761 handleNotificationError(sbn, "Couldn't create icon: " + ic); 1762 return null; 1763 } 1764 // Construct the expanded view. 1765 NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView); 1766 if (!inflateViews(entry, mStackScroller)) { 1767 handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn); 1768 return null; 1769 } 1770 return entry; 1771 } 1772 1773 protected void addNotificationViews(Entry entry, RankingMap ranking) { 1774 if (entry == null) { 1775 return; 1776 } 1777 // Add the expanded view and icon. 1778 mNotificationData.add(entry, ranking); 1779 updateNotifications(); 1780 } 1781 1782 /** 1783 * @return The number of notifications we show on Keyguard. 1784 */ 1785 protected abstract int getMaxKeyguardNotifications(); 1786 1787 /** 1788 * Updates expanded, dimmed and locked states of notification rows. 1789 */ 1790 protected void updateRowStates() { 1791 int maxKeyguardNotifications = getMaxKeyguardNotifications(); 1792 mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); 1793 1794 ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); 1795 final int N = activeNotifications.size(); 1796 1797 int visibleNotifications = 0; 1798 boolean onKeyguard = mState == StatusBarState.KEYGUARD; 1799 for (int i = 0; i < N; i++) { 1800 NotificationData.Entry entry = activeNotifications.get(i); 1801 if (onKeyguard) { 1802 entry.row.setExpansionDisabled(true); 1803 } else { 1804 entry.row.setExpansionDisabled(false); 1805 if (!entry.row.isUserLocked()) { 1806 boolean top = (i == 0); 1807 entry.row.setSystemExpanded(top); 1808 } 1809 } 1810 boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); 1811 if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) || 1812 (onKeyguard && (visibleNotifications >= maxKeyguardNotifications 1813 || !showOnKeyguard))) { 1814 entry.row.setVisibility(View.GONE); 1815 if (onKeyguard && showOnKeyguard) { 1816 mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); 1817 } 1818 } else { 1819 boolean wasGone = entry.row.getVisibility() == View.GONE; 1820 entry.row.setVisibility(View.VISIBLE); 1821 if (wasGone) { 1822 // notify the scroller of a child addition 1823 mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */); 1824 } 1825 visibleNotifications++; 1826 } 1827 } 1828 1829 if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { 1830 mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE); 1831 } else { 1832 mKeyguardIconOverflowContainer.setVisibility(View.GONE); 1833 } 1834 1835 mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, 1836 mStackScroller.getChildCount() - 3); 1837 mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2); 1838 mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1); 1839 } 1840 1841 private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { 1842 return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey()); 1843 } 1844 1845 protected void setZenMode(int mode) { 1846 if (!isDeviceProvisioned()) return; 1847 mZenMode = mode; 1848 updateNotifications(); 1849 } 1850 1851 // extended in PhoneStatusBar 1852 protected void setShowLockscreenNotifications(boolean show) { 1853 mShowLockscreenNotifications = show; 1854 } 1855 1856 private void updateLockscreenNotificationSetting() { 1857 final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(), 1858 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1859 1, 1860 mCurrentUserId) != 0; 1861 final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( 1862 null /* admin */, mCurrentUserId); 1863 final boolean allowedByDpm = (dpmFlags 1864 & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; 1865 setShowLockscreenNotifications(show && allowedByDpm); 1866 } 1867 1868 protected abstract void haltTicker(); 1869 protected abstract void setAreThereNotifications(); 1870 protected abstract void updateNotifications(); 1871 protected abstract void tick(StatusBarNotification n, boolean firstTime); 1872 protected abstract void updateExpandedViewPos(int expandedPosition); 1873 protected abstract boolean shouldDisableNavbarGestures(); 1874 1875 public abstract void addNotification(StatusBarNotification notification, 1876 RankingMap ranking); 1877 protected abstract void updateNotificationRanking(RankingMap ranking); 1878 public abstract void removeNotification(String key, RankingMap ranking); 1879 1880 public void updateNotification(StatusBarNotification notification, RankingMap ranking) { 1881 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); 1882 1883 final String key = notification.getKey(); 1884 boolean wasHeadsUp = isHeadsUp(key); 1885 Entry oldEntry; 1886 if (wasHeadsUp) { 1887 oldEntry = mHeadsUpNotificationView.getEntry(); 1888 } else { 1889 oldEntry = mNotificationData.get(key); 1890 } 1891 if (oldEntry == null) { 1892 return; 1893 } 1894 1895 final StatusBarNotification oldNotification = oldEntry.notification; 1896 1897 // XXX: modify when we do something more intelligent with the two content views 1898 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 1899 Notification n = notification.getNotification(); 1900 final RemoteViews contentView = n.contentView; 1901 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 1902 final RemoteViews bigContentView = n.bigContentView; 1903 final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView; 1904 final RemoteViews headsUpContentView = n.headsUpContentView; 1905 final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; 1906 final RemoteViews oldPublicContentView = oldPublicNotification != null 1907 ? oldPublicNotification.contentView : null; 1908 final Notification publicNotification = n.publicVersion; 1909 final RemoteViews publicContentView = publicNotification != null 1910 ? publicNotification.contentView : null; 1911 1912 if (DEBUG) { 1913 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 1914 + " ongoing=" + oldNotification.isOngoing() 1915 + " expanded=" + oldEntry.expanded 1916 + " contentView=" + oldContentView 1917 + " bigContentView=" + oldBigContentView 1918 + " publicView=" + oldPublicContentView 1919 + " rowParent=" + oldEntry.row.getParent()); 1920 Log.d(TAG, "new notification: when=" + n.when 1921 + " ongoing=" + oldNotification.isOngoing() 1922 + " contentView=" + contentView 1923 + " bigContentView=" + bigContentView 1924 + " publicView=" + publicContentView); 1925 } 1926 1927 // Can we just reapply the RemoteViews in place? 1928 1929 // 1U is never null 1930 boolean contentsUnchanged = oldEntry.expanded != null 1931 && contentView.getPackage() != null 1932 && oldContentView.getPackage() != null 1933 && oldContentView.getPackage().equals(contentView.getPackage()) 1934 && oldContentView.getLayoutId() == contentView.getLayoutId(); 1935 // large view may be null 1936 boolean bigContentsUnchanged = 1937 (oldEntry.getBigContentView() == null && bigContentView == null) 1938 || ((oldEntry.getBigContentView() != null && bigContentView != null) 1939 && bigContentView.getPackage() != null 1940 && oldBigContentView.getPackage() != null 1941 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 1942 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 1943 boolean headsUpContentsUnchanged = 1944 (oldHeadsUpContentView == null && headsUpContentView == null) 1945 || ((oldHeadsUpContentView != null && headsUpContentView != null) 1946 && headsUpContentView.getPackage() != null 1947 && oldHeadsUpContentView.getPackage() != null 1948 && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) 1949 && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); 1950 boolean publicUnchanged = 1951 (oldPublicContentView == null && publicContentView == null) 1952 || ((oldPublicContentView != null && publicContentView != null) 1953 && publicContentView.getPackage() != null 1954 && oldPublicContentView.getPackage() != null 1955 && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) 1956 && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); 1957 boolean updateTicker = n.tickerText != null 1958 && !TextUtils.equals(n.tickerText, 1959 oldEntry.notification.getNotification().tickerText); 1960 1961 final boolean shouldInterrupt = shouldInterrupt(notification); 1962 final boolean alertAgain = alertAgain(oldEntry, n); 1963 boolean updateSuccessful = false; 1964 if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged 1965 && publicUnchanged) { 1966 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); 1967 oldEntry.notification = notification; 1968 try { 1969 if (oldEntry.icon != null) { 1970 // Update the icon 1971 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1972 notification.getUser(), 1973 n.icon, 1974 n.iconLevel, 1975 n.number, 1976 n.tickerText); 1977 oldEntry.icon.setNotification(n); 1978 if (!oldEntry.icon.set(ic)) { 1979 handleNotificationError(notification, "Couldn't update icon: " + ic); 1980 return; 1981 } 1982 } 1983 1984 if (wasHeadsUp) { 1985 if (shouldInterrupt) { 1986 updateHeadsUpViews(oldEntry, notification); 1987 if (alertAgain) { 1988 resetHeadsUpDecayTimer(); 1989 } 1990 } else { 1991 // we updated the notification above, so release to build a new shade entry 1992 mHeadsUpNotificationView.releaseAndClose(); 1993 return; 1994 } 1995 } else { 1996 if (shouldInterrupt && alertAgain) { 1997 removeNotificationViews(key, ranking); 1998 addNotification(notification, ranking); //this will pop the headsup 1999 } else { 2000 updateNotificationViews(oldEntry, notification); 2001 } 2002 } 2003 mNotificationData.updateRanking(ranking); 2004 updateNotifications(); 2005 updateSuccessful = true; 2006 } 2007 catch (RuntimeException e) { 2008 // It failed to add cleanly. Log, and remove the view from the panel. 2009 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 2010 } 2011 } 2012 if (!updateSuccessful) { 2013 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); 2014 if (wasHeadsUp) { 2015 if (shouldInterrupt) { 2016 if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key); 2017 Entry newEntry = new Entry(notification, null); 2018 ViewGroup holder = mHeadsUpNotificationView.getHolder(); 2019 if (inflateViewsForHeadsUp(newEntry, holder)) { 2020 mHeadsUpNotificationView.showNotification(newEntry); 2021 if (alertAgain) { 2022 resetHeadsUpDecayTimer(); 2023 } 2024 } else { 2025 Log.w(TAG, "Couldn't create new updated headsup for package " 2026 + contentView.getPackage()); 2027 } 2028 } else { 2029 if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key); 2030 oldEntry.notification = notification; 2031 mHeadsUpNotificationView.releaseAndClose(); 2032 return; 2033 } 2034 } else { 2035 if (shouldInterrupt && alertAgain) { 2036 if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key); 2037 removeNotificationViews(key, ranking); 2038 addNotification(notification, ranking); //this will pop the headsup 2039 } else { 2040 if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key); 2041 oldEntry.notification = notification; 2042 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 2043 notification.getUser(), 2044 n.icon, 2045 n.iconLevel, 2046 n.number, 2047 n.tickerText); 2048 oldEntry.icon.setNotification(n); 2049 oldEntry.icon.set(ic); 2050 inflateViews(oldEntry, mStackScroller, wasHeadsUp); 2051 mNotificationData.updateRanking(ranking); 2052 updateNotifications(); 2053 } 2054 } 2055 } 2056 2057 // Update the veto button accordingly (and as a result, whether this row is 2058 // swipe-dismissable) 2059 updateNotificationVetoButton(oldEntry.row, notification); 2060 2061 // Is this for you? 2062 boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); 2063 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 2064 2065 // Restart the ticker if it's still running 2066 if (updateTicker && isForCurrentUser) { 2067 haltTicker(); 2068 tick(notification, false); 2069 } 2070 2071 // Recalculate the position of the sliding windows and the titles. 2072 setAreThereNotifications(); 2073 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 2074 } 2075 2076 private void updateNotificationViews(NotificationData.Entry entry, 2077 StatusBarNotification notification) { 2078 updateNotificationViews(entry, notification, false); 2079 } 2080 2081 private void updateHeadsUpViews(NotificationData.Entry entry, 2082 StatusBarNotification notification) { 2083 updateNotificationViews(entry, notification, true); 2084 } 2085 2086 private void updateNotificationViews(NotificationData.Entry entry, 2087 StatusBarNotification notification, boolean isHeadsUp) { 2088 final RemoteViews contentView = notification.getNotification().contentView; 2089 final RemoteViews bigContentView = isHeadsUp 2090 ? notification.getNotification().headsUpContentView 2091 : notification.getNotification().bigContentView; 2092 final Notification publicVersion = notification.getNotification().publicVersion; 2093 final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView 2094 : null; 2095 2096 // Reapply the RemoteViews 2097 contentView.reapply(mContext, entry.expanded, mOnClickHandler); 2098 if (bigContentView != null && entry.getBigContentView() != null) { 2099 bigContentView.reapply(mContext, entry.getBigContentView(), 2100 mOnClickHandler); 2101 } 2102 if (publicContentView != null && entry.getPublicContentView() != null) { 2103 publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); 2104 } 2105 // update the contentIntent 2106 final PendingIntent contentIntent = notification.getNotification().contentIntent; 2107 if (contentIntent != null) { 2108 final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(), 2109 isHeadsUp); 2110 entry.row.setOnClickListener(listener); 2111 } else { 2112 entry.row.setOnClickListener(null); 2113 } 2114 entry.row.setStatusBarNotification(notification); 2115 entry.row.notifyContentUpdated(); 2116 entry.row.resetHeight(); 2117 } 2118 2119 protected void notifyHeadsUpScreenOn(boolean screenOn) { 2120 if (!screenOn) { 2121 scheduleHeadsUpEscalation(); 2122 } 2123 } 2124 2125 private boolean alertAgain(Entry oldEntry, Notification newNotification) { 2126 return oldEntry == null || !oldEntry.hasInterrupted() 2127 || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; 2128 } 2129 2130 protected boolean shouldInterrupt(StatusBarNotification sbn) { 2131 if (mNotificationData.shouldFilterOut(sbn)) { 2132 if (DEBUG) { 2133 Log.d(TAG, "Skipping HUN check for " + sbn.getKey() + " since it's filtered out."); 2134 } 2135 return false; 2136 } 2137 2138 if (mHeadsUpNotificationView.isSnoozed(sbn.getPackageName())) { 2139 return false; 2140 } 2141 2142 Notification notification = sbn.getNotification(); 2143 // some predicates to make the boolean logic legible 2144 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 2145 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 2146 || notification.sound != null 2147 || notification.vibrate != null; 2148 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 2149 boolean isFullscreen = notification.fullScreenIntent != null; 2150 boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText); 2151 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 2152 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 2153 boolean accessibilityForcesLaunch = isFullscreen 2154 && mAccessibilityManager.isTouchExplorationEnabled(); 2155 2156 boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker))) 2157 && isAllowed 2158 && !accessibilityForcesLaunch 2159 && mPowerManager.isScreenOn() 2160 && (!mStatusBarKeyguardViewManager.isShowing() 2161 || mStatusBarKeyguardViewManager.isOccluded()) 2162 && !mStatusBarKeyguardViewManager.isInputRestricted(); 2163 try { 2164 interrupt = interrupt && !mDreamManager.isDreaming(); 2165 } catch (RemoteException e) { 2166 Log.d(TAG, "failed to query dream manager", e); 2167 } 2168 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 2169 return interrupt; 2170 } 2171 2172 public void setInteracting(int barWindow, boolean interacting) { 2173 // hook for subclasses 2174 } 2175 2176 public void setBouncerShowing(boolean bouncerShowing) { 2177 mBouncerShowing = bouncerShowing; 2178 } 2179 2180 /** 2181 * @return Whether the security bouncer from Keyguard is showing. 2182 */ 2183 public boolean isBouncerShowing() { 2184 return mBouncerShowing; 2185 } 2186 2187 public void destroy() { 2188 if (mSearchPanelView != null) { 2189 mWindowManager.removeViewImmediate(mSearchPanelView); 2190 } 2191 mContext.unregisterReceiver(mBroadcastReceiver); 2192 try { 2193 mNotificationListener.unregisterAsSystemService(); 2194 } catch (RemoteException e) { 2195 // Ignore. 2196 } 2197 } 2198 2199 /** 2200 * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then 2201 * return PackageManager for mContext 2202 */ 2203 protected PackageManager getPackageManagerForUser(int userId) { 2204 Context contextForUser = mContext; 2205 // UserHandle defines special userId as negative values, e.g. USER_ALL 2206 if (userId >= 0) { 2207 try { 2208 // Create a context for the correct user so if a package isn't installed 2209 // for user 0 we can still load information about the package. 2210 contextForUser = 2211 mContext.createPackageContextAsUser(mContext.getPackageName(), 2212 Context.CONTEXT_RESTRICTED, 2213 new UserHandle(userId)); 2214 } catch (NameNotFoundException e) { 2215 // Shouldn't fail to find the package name for system ui. 2216 } 2217 } 2218 return contextForUser.getPackageManager(); 2219 } 2220 2221 @Override 2222 public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { 2223 try { 2224 mBarService.onNotificationExpansionChanged(key, userAction, expanded); 2225 } catch (RemoteException e) { 2226 // Ignore. 2227 } 2228 } 2229 2230 public boolean isKeyguardSecure() { 2231 if (mStatusBarKeyguardViewManager == null) { 2232 // startKeyguard() hasn't been called yet, so we don't know. 2233 // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this 2234 // value onVisibilityChanged(). 2235 Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false", 2236 new Throwable()); 2237 return false; 2238 } 2239 return mStatusBarKeyguardViewManager.isSecure(); 2240 } 2241 } 2242