BaseStatusBar.java revision 44fc65f6d0561b382b4e48e35a014344de93eee2
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.TimeInterpolator; 22import android.app.ActivityManager; 23import android.app.ActivityManagerNative; 24import android.app.Notification; 25import android.app.NotificationManager; 26import android.app.PendingIntent; 27import android.app.TaskStackBuilder; 28import android.app.admin.DevicePolicyManager; 29import android.content.BroadcastReceiver; 30import android.content.ComponentName; 31import android.content.Context; 32import android.content.Intent; 33import android.content.IntentFilter; 34import android.content.pm.ApplicationInfo; 35import android.content.pm.PackageManager; 36import android.content.pm.PackageManager.NameNotFoundException; 37import android.content.pm.ResolveInfo; 38import android.content.pm.UserInfo; 39import android.content.res.Configuration; 40import android.content.res.Resources; 41import android.database.ContentObserver; 42import android.graphics.PorterDuff; 43import android.graphics.drawable.Drawable; 44import android.os.AsyncTask; 45import android.os.Build; 46import android.os.Handler; 47import android.os.IBinder; 48import android.os.Message; 49import android.os.PowerManager; 50import android.os.RemoteException; 51import android.os.ServiceManager; 52import android.os.UserHandle; 53import android.os.UserManager; 54import android.provider.Settings; 55import android.service.dreams.DreamService; 56import android.service.dreams.IDreamManager; 57import android.service.notification.NotificationListenerService; 58import android.service.notification.NotificationListenerService.RankingMap; 59import android.service.notification.StatusBarNotification; 60import android.text.TextUtils; 61import android.util.Log; 62import android.util.SparseArray; 63import android.util.SparseBooleanArray; 64import android.view.Display; 65import android.view.IWindowManager; 66import android.view.LayoutInflater; 67import android.view.MotionEvent; 68import android.view.View; 69import android.view.ViewAnimationUtils; 70import android.view.ViewGroup; 71import android.view.ViewGroup.LayoutParams; 72import android.view.ViewParent; 73import android.view.ViewStub; 74import android.view.WindowManager; 75import android.view.WindowManagerGlobal; 76import android.view.accessibility.AccessibilityManager; 77import android.view.animation.AnimationUtils; 78import android.widget.DateTimeView; 79import android.widget.ImageView; 80import android.widget.LinearLayout; 81import android.widget.RemoteViews; 82import android.widget.TextView; 83 84import com.android.internal.statusbar.IStatusBarService; 85import com.android.internal.statusbar.StatusBarIcon; 86import com.android.internal.statusbar.StatusBarIconList; 87import com.android.internal.util.NotificationColorUtil; 88import com.android.internal.widget.LockPatternUtils; 89import com.android.systemui.R; 90import com.android.systemui.RecentsComponent; 91import com.android.systemui.SearchPanelView; 92import com.android.systemui.SwipeHelper; 93import com.android.systemui.SystemUI; 94import com.android.systemui.statusbar.NotificationData.Entry; 95import com.android.systemui.statusbar.phone.NavigationBarView; 96import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 97import com.android.systemui.statusbar.policy.HeadsUpNotificationView; 98import com.android.systemui.statusbar.policy.PreviewInflater; 99import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 100 101import java.util.ArrayList; 102import java.util.List; 103import java.util.Locale; 104 105import static com.android.keyguard.KeyguardHostView.OnDismissAction; 106 107public 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 */ 212 protected abstract void createAndAddWindows(); 213 214 protected WindowManager mWindowManager; 215 protected IWindowManager mWindowManagerService; 216 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 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 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 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 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 664 public void userSwitched(int newUserId) { 665 setHeadsUpUser(newUserId); 666 } 667 668 private void setHeadsUpUser(int newUserId) { 669 if (mHeadsUpNotificationView != null) { 670 mHeadsUpNotificationView.setUser(newUserId); 671 } 672 } 673 674 public boolean isHeadsUp(String key) { 675 return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key); 676 } 677 678 @Override // NotificationData.Environment 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 synchronized (mCurrentProfiles) { 687 return notificationUserId == UserHandle.USER_ALL 688 || mCurrentProfiles.get(notificationUserId) != null; 689 } 690 } 691 692 protected boolean isCurrentProfile(int userId) { 693 synchronized (mCurrentProfiles) { 694 return mCurrentProfiles.get(userId) != null; 695 } 696 } 697 698 @Override 699 public String getCurrentMediaNotificationKey() { 700 return null; 701 } 702 703 /** 704 * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. 705 * @param action A dismiss action that is called if it's safe to start the activity. 706 * @param afterKeyguardGone Whether the action should be executed after the Keyguard is gone. 707 */ 708 protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { 709 action.onDismiss(); 710 } 711 712 @Override 713 protected void onConfigurationChanged(Configuration newConfig) { 714 final Locale locale = mContext.getResources().getConfiguration().locale; 715 final int ld = TextUtils.getLayoutDirectionFromLocale(locale); 716 final float fontScale = newConfig.fontScale; 717 718 if (! locale.equals(mLocale) || ld != mLayoutDirection || fontScale != mFontScale) { 719 if (DEBUG) { 720 Log.v(TAG, String.format( 721 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, 722 locale, ld)); 723 } 724 mLocale = locale; 725 mLayoutDirection = ld; 726 refreshLayout(ld); 727 } 728 } 729 730 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 731 View vetoButton = row.findViewById(R.id.veto); 732 if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null 733 && mHeadsUpNotificationView.getEntry().row == row)) { 734 final String _pkg = n.getPackageName(); 735 final String _tag = n.getTag(); 736 final int _id = n.getId(); 737 final int _userId = n.getUserId(); 738 vetoButton.setOnClickListener(new View.OnClickListener() { 739 public void onClick(View v) { 740 // Accessibility feedback 741 v.announceForAccessibility( 742 mContext.getString(R.string.accessibility_notification_dismissed)); 743 try { 744 mBarService.onNotificationClear(_pkg, _tag, _id, _userId); 745 746 } catch (RemoteException ex) { 747 // system process is dead if we're here. 748 } 749 } 750 }); 751 vetoButton.setVisibility(View.VISIBLE); 752 } else { 753 vetoButton.setVisibility(View.GONE); 754 } 755 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 756 return vetoButton; 757 } 758 759 760 protected void applyColorsAndBackgrounds(StatusBarNotification sbn, 761 NotificationData.Entry entry) { 762 763 if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) { 764 // Using custom RemoteViews 765 if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD 766 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) { 767 entry.row.setShowingLegacyBackground(true); 768 entry.legacy = true; 769 } 770 } else { 771 // Using platform templates 772 final int color = sbn.getNotification().color; 773 if (isMediaNotification(entry)) { 774 entry.row.setTintColor(color == Notification.COLOR_DEFAULT 775 ? mContext.getResources().getColor( 776 R.color.notification_material_background_media_default_color) 777 : color); 778 } 779 } 780 781 if (entry.icon != null) { 782 if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP) { 783 entry.icon.setColorFilter(mContext.getResources().getColor(android.R.color.white)); 784 } else { 785 entry.icon.setColorFilter(null); 786 } 787 } 788 } 789 790 public boolean isMediaNotification(NotificationData.Entry entry) { 791 // TODO: confirm that there's a valid media key 792 return entry.expandedBig != null && 793 entry.expandedBig.findViewById(com.android.internal.R.id.media_actions) != null; 794 } 795 796 // The gear button in the guts that links to the app's own notification settings 797 private void startAppOwnNotificationSettingsActivity(Intent intent, 798 final int notificationId, final String notificationTag, final int appUid) { 799 intent.putExtra("notification_id", notificationId); 800 intent.putExtra("notification_tag", notificationTag); 801 startNotificationGutsIntent(intent, appUid); 802 } 803 804 // The (i) button in the guts that links to the system notification settings for that app 805 private void startAppNotificationSettingsActivity(String packageName, final int appUid) { 806 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 807 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); 808 intent.putExtra(Settings.EXTRA_APP_UID, appUid); 809 startNotificationGutsIntent(intent, appUid); 810 } 811 812 private void startNotificationGutsIntent(final Intent intent, final int appUid) { 813 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 814 dismissKeyguardThenExecute(new OnDismissAction() { 815 @Override 816 public boolean onDismiss() { 817 AsyncTask.execute(new Runnable() { 818 public void run() { 819 try { 820 if (keyguardShowing) { 821 ActivityManagerNative.getDefault() 822 .keyguardWaitingForActivityDrawn(); 823 } 824 TaskStackBuilder.create(mContext) 825 .addNextIntentWithParentStack(intent) 826 .startActivities(null, 827 new UserHandle(UserHandle.getUserId(appUid))); 828 overrideActivityPendingAppTransition(keyguardShowing); 829 } catch (RemoteException e) { 830 } 831 } 832 }); 833 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); 834 return true; 835 } 836 }, false /* afterKeyguardGone */); 837 } 838 839 private void inflateGuts(ExpandableNotificationRow row) { 840 ViewStub stub = (ViewStub) row.findViewById(R.id.notification_guts_stub); 841 if (stub != null) { 842 stub.inflate(); 843 } 844 final StatusBarNotification sbn = row.getStatusBarNotification(); 845 PackageManager pmUser = getPackageManagerForUser( 846 sbn.getUser().getIdentifier()); 847 row.setTag(sbn.getPackageName()); 848 final View guts = row.findViewById(R.id.notification_guts); 849 final String pkg = sbn.getPackageName(); 850 String appname = pkg; 851 Drawable pkgicon = null; 852 int appUid = -1; 853 try { 854 final ApplicationInfo info = pmUser.getApplicationInfo(pkg, 855 PackageManager.GET_UNINSTALLED_PACKAGES 856 | PackageManager.GET_DISABLED_COMPONENTS); 857 if (info != null) { 858 appname = String.valueOf(pmUser.getApplicationLabel(info)); 859 pkgicon = pmUser.getApplicationIcon(info); 860 appUid = info.uid; 861 } 862 } catch (NameNotFoundException e) { 863 // app is gone, just show package name and generic icon 864 pkgicon = pmUser.getDefaultActivityIcon(); 865 } 866 ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon); 867 ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(sbn.getPostTime()); 868 ((TextView) row.findViewById(R.id.pkgname)).setText(appname); 869 final View settingsButton = guts.findViewById(R.id.notification_inspect_item); 870 final View appSettingsButton 871 = guts.findViewById(R.id.notification_inspect_app_provided_settings); 872 if (appUid >= 0) { 873 final int appUidF = appUid; 874 settingsButton.setOnClickListener(new View.OnClickListener() { 875 public void onClick(View v) { 876 startAppNotificationSettingsActivity(pkg, appUidF); 877 } 878 }); 879 880 final Intent appSettingsQueryIntent 881 = new Intent(Intent.ACTION_MAIN) 882 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) 883 .setPackage(pkg); 884 List<ResolveInfo> infos = pmUser.queryIntentActivities(appSettingsQueryIntent, 0); 885 if (infos.size() > 0) { 886 appSettingsButton.setVisibility(View.VISIBLE); 887 appSettingsButton.setContentDescription( 888 mContext.getResources().getString( 889 R.string.status_bar_notification_app_settings_title, 890 appname 891 )); 892 final Intent appSettingsLaunchIntent = new Intent(appSettingsQueryIntent) 893 .setClassName(pkg, infos.get(0).activityInfo.name); 894 appSettingsButton.setOnClickListener(new View.OnClickListener() { 895 public void onClick(View v) { 896 startAppOwnNotificationSettingsActivity(appSettingsLaunchIntent, 897 sbn.getId(), 898 sbn.getTag(), 899 appUidF); 900 } 901 }); 902 } else { 903 appSettingsButton.setVisibility(View.GONE); 904 } 905 } else { 906 settingsButton.setVisibility(View.GONE); 907 appSettingsButton.setVisibility(View.GONE); 908 } 909 910 } 911 912 protected SwipeHelper.LongPressListener getNotificationLongClicker() { 913 return new SwipeHelper.LongPressListener() { 914 @Override 915 public boolean onLongPress(View v, int x, int y) { 916 dismissPopups(); 917 918 if (!(v instanceof ExpandableNotificationRow)) { 919 return false; 920 } 921 if (v.getWindowToken() == null) { 922 Log.e(TAG, "Trying to show notification guts, but not attached to window"); 923 return false; 924 } 925 926 inflateGuts((ExpandableNotificationRow) v); 927 928 // Assume we are a status_bar_notification_row 929 final NotificationGuts guts = (NotificationGuts) v.findViewById( 930 R.id.notification_guts); 931 if (guts == null) { 932 // This view has no guts. Examples are the more card or the dismiss all view 933 return false; 934 } 935 936 // Already showing? 937 if (guts.getVisibility() == View.VISIBLE) { 938 Log.e(TAG, "Trying to show notification guts, but already visible"); 939 return false; 940 } 941 942 guts.setVisibility(View.VISIBLE); 943 final double horz = Math.max(guts.getWidth() - x, x); 944 final double vert = Math.max(guts.getActualHeight() - y, y); 945 final float r = (float) Math.hypot(horz, vert); 946 final Animator a 947 = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r); 948 a.setDuration(400); 949 a.setInterpolator(mLinearOutSlowIn); 950 a.start(); 951 952 mNotificationGutsExposed = guts; 953 954 return true; 955 } 956 }; 957 } 958 959 public void dismissPopups() { 960 if (mNotificationGutsExposed != null) { 961 final NotificationGuts v = mNotificationGutsExposed; 962 mNotificationGutsExposed = null; 963 964 if (v.getWindowToken() == null) return; 965 966 final int x = (v.getLeft() + v.getRight()) / 2; 967 final int y = (v.getTop() + v.getActualHeight() / 2); 968 final Animator a = ViewAnimationUtils.createCircularReveal(v, 969 x, y, x, 0); 970 a.setDuration(200); 971 a.setInterpolator(mFastOutLinearIn); 972 a.addListener(new AnimatorListenerAdapter() { 973 @Override 974 public void onAnimationEnd(Animator animation) { 975 super.onAnimationEnd(animation); 976 v.setVisibility(View.GONE); 977 } 978 }); 979 a.start(); 980 } 981 } 982 983 public void onHeadsUpDismissed() { 984 } 985 986 @Override 987 public void showRecentApps(boolean triggeredFromAltTab) { 988 int msg = MSG_SHOW_RECENT_APPS; 989 mHandler.removeMessages(msg); 990 mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 0).sendToTarget(); 991 } 992 993 @Override 994 public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 995 int msg = MSG_HIDE_RECENT_APPS; 996 mHandler.removeMessages(msg); 997 mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 998 triggeredFromHomeKey ? 1 : 0).sendToTarget(); 999 } 1000 1001 @Override 1002 public void toggleRecentApps() { 1003 int msg = MSG_TOGGLE_RECENTS_APPS; 1004 mHandler.removeMessages(msg); 1005 mHandler.sendEmptyMessage(msg); 1006 } 1007 1008 @Override 1009 public void preloadRecentApps() { 1010 int msg = MSG_PRELOAD_RECENT_APPS; 1011 mHandler.removeMessages(msg); 1012 mHandler.sendEmptyMessage(msg); 1013 } 1014 1015 @Override 1016 public void cancelPreloadRecentApps() { 1017 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 1018 mHandler.removeMessages(msg); 1019 mHandler.sendEmptyMessage(msg); 1020 } 1021 1022 /** Jumps to the next affiliated task in the group. */ 1023 public void showNextAffiliatedTask() { 1024 int msg = MSG_SHOW_NEXT_AFFILIATED_TASK; 1025 mHandler.removeMessages(msg); 1026 mHandler.sendEmptyMessage(msg); 1027 } 1028 1029 /** Jumps to the previous affiliated task in the group. */ 1030 public void showPreviousAffiliatedTask() { 1031 int msg = MSG_SHOW_PREV_AFFILIATED_TASK; 1032 mHandler.removeMessages(msg); 1033 mHandler.sendEmptyMessage(msg); 1034 } 1035 1036 @Override 1037 public void showSearchPanel() { 1038 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 1039 mSearchPanelView.show(true, true); 1040 } 1041 } 1042 1043 @Override 1044 public void hideSearchPanel() { 1045 int msg = MSG_CLOSE_SEARCH_PANEL; 1046 mHandler.removeMessages(msg); 1047 mHandler.sendEmptyMessage(msg); 1048 } 1049 1050 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 1051 LayoutParams layoutParams); 1052 1053 protected void updateSearchPanel() { 1054 // Search Panel 1055 boolean visible = false; 1056 if (mSearchPanelView != null) { 1057 visible = mSearchPanelView.isShowing(); 1058 mWindowManager.removeView(mSearchPanelView); 1059 } 1060 1061 // Provide SearchPanel with a temporary parent to allow layout params to work. 1062 LinearLayout tmpRoot = new LinearLayout(mContext); 1063 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 1064 R.layout.status_bar_search_panel, tmpRoot, false); 1065 mSearchPanelView.setOnTouchListener( 1066 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 1067 mSearchPanelView.setVisibility(View.GONE); 1068 boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical(); 1069 mSearchPanelView.setHorizontal(vertical); 1070 1071 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 1072 1073 mWindowManager.addView(mSearchPanelView, lp); 1074 mSearchPanelView.setBar(this); 1075 if (visible) { 1076 mSearchPanelView.show(true, false); 1077 } 1078 } 1079 1080 protected H createHandler() { 1081 return new H(); 1082 } 1083 1084 static void sendCloseSystemWindows(Context context, String reason) { 1085 if (ActivityManagerNative.isSystemReady()) { 1086 try { 1087 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 1088 } catch (RemoteException e) { 1089 } 1090 } 1091 } 1092 1093 protected abstract View getStatusBarView(); 1094 1095 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 1096 // additional optimization when we have software system buttons - start loading the recent 1097 // tasks on touch down 1098 @Override 1099 public boolean onTouch(View v, MotionEvent event) { 1100 int action = event.getAction() & MotionEvent.ACTION_MASK; 1101 if (action == MotionEvent.ACTION_DOWN) { 1102 preloadRecents(); 1103 } else if (action == MotionEvent.ACTION_CANCEL) { 1104 cancelPreloadingRecents(); 1105 } else if (action == MotionEvent.ACTION_UP) { 1106 if (!v.isPressed()) { 1107 cancelPreloadingRecents(); 1108 } 1109 1110 } 1111 return false; 1112 } 1113 }; 1114 1115 /** Proxy for RecentsComponent */ 1116 1117 protected void showRecents(boolean triggeredFromAltTab) { 1118 if (mRecents != null) { 1119 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 1120 mRecents.showRecents(triggeredFromAltTab, getStatusBarView()); 1121 } 1122 } 1123 1124 protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 1125 if (mRecents != null) { 1126 mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey); 1127 } 1128 } 1129 1130 protected void toggleRecents() { 1131 if (mRecents != null) { 1132 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 1133 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView()); 1134 } 1135 } 1136 1137 protected void preloadRecents() { 1138 if (mRecents != null) { 1139 mRecents.preloadRecents(); 1140 } 1141 } 1142 1143 protected void cancelPreloadingRecents() { 1144 if (mRecents != null) { 1145 mRecents.cancelPreloadingRecents(); 1146 } 1147 } 1148 1149 protected void showRecentsNextAffiliatedTask() { 1150 if (mRecents != null) { 1151 mRecents.showNextAffiliatedTask(); 1152 } 1153 } 1154 1155 protected void showRecentsPreviousAffiliatedTask() { 1156 if (mRecents != null) { 1157 mRecents.showPrevAffiliatedTask(); 1158 } 1159 } 1160 1161 @Override 1162 public void onVisibilityChanged(boolean visible) { 1163 // Do nothing 1164 } 1165 1166 public abstract void resetHeadsUpDecayTimer(); 1167 1168 public abstract void scheduleHeadsUpOpen(); 1169 1170 public abstract void scheduleHeadsUpClose(); 1171 1172 public abstract void scheduleHeadsUpEscalation(); 1173 1174 /** 1175 * Save the current "public" (locked and secure) state of the lockscreen. 1176 */ 1177 public void setLockscreenPublicMode(boolean publicMode) { 1178 mLockscreenPublicMode = publicMode; 1179 } 1180 1181 public boolean isLockscreenPublicMode() { 1182 return mLockscreenPublicMode; 1183 } 1184 1185 /** 1186 * Has the given user chosen to allow their private (full) notifications to be shown even 1187 * when the lockscreen is in "public" (secure & locked) mode? 1188 */ 1189 public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { 1190 if (userHandle == UserHandle.USER_ALL) { 1191 return true; 1192 } 1193 1194 if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { 1195 final boolean allowed = 0 != Settings.Secure.getIntForUser( 1196 mContext.getContentResolver(), 1197 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); 1198 final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, 1199 userHandle); 1200 final boolean allowedByDpm = (dpmFlags 1201 & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; 1202 mUsersAllowingPrivateNotifications.append(userHandle, allowed && allowedByDpm); 1203 return allowed; 1204 } 1205 1206 return mUsersAllowingPrivateNotifications.get(userHandle); 1207 } 1208 1209 /** 1210 * Returns true if we're on a secure lockscreen and the user wants to hide "sensitive" 1211 * notification data. If so, private notifications should show their (possibly 1212 * auto-generated) publicVersion, and secret notifications should be totally invisible. 1213 */ 1214 @Override // NotificationData.Environment 1215 public boolean shouldHideSensitiveContents(int userid) { 1216 return isLockscreenPublicMode() && !userAllowsPrivateNotificationsInPublic(userid); 1217 } 1218 1219 public void onNotificationClear(StatusBarNotification notification) { 1220 try { 1221 mBarService.onNotificationClear( 1222 notification.getPackageName(), 1223 notification.getTag(), 1224 notification.getId(), 1225 notification.getUserId()); 1226 } catch (android.os.RemoteException ex) { 1227 // oh well 1228 } 1229 } 1230 1231 protected class H extends Handler { 1232 public void handleMessage(Message m) { 1233 switch (m.what) { 1234 case MSG_SHOW_RECENT_APPS: 1235 showRecents(m.arg1 > 0); 1236 break; 1237 case MSG_HIDE_RECENT_APPS: 1238 hideRecents(m.arg1 > 0, m.arg2 > 0); 1239 break; 1240 case MSG_TOGGLE_RECENTS_APPS: 1241 toggleRecents(); 1242 break; 1243 case MSG_PRELOAD_RECENT_APPS: 1244 preloadRecents(); 1245 break; 1246 case MSG_CANCEL_PRELOAD_RECENT_APPS: 1247 cancelPreloadingRecents(); 1248 break; 1249 case MSG_SHOW_NEXT_AFFILIATED_TASK: 1250 showRecentsNextAffiliatedTask(); 1251 break; 1252 case MSG_SHOW_PREV_AFFILIATED_TASK: 1253 showRecentsPreviousAffiliatedTask(); 1254 break; 1255 case MSG_CLOSE_SEARCH_PANEL: 1256 if (DEBUG) Log.d(TAG, "closing search panel"); 1257 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 1258 mSearchPanelView.show(false, true); 1259 } 1260 break; 1261 } 1262 } 1263 } 1264 1265 public class TouchOutsideListener implements View.OnTouchListener { 1266 private int mMsg; 1267 private StatusBarPanel mPanel; 1268 1269 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1270 mMsg = msg; 1271 mPanel = panel; 1272 } 1273 1274 public boolean onTouch(View v, MotionEvent ev) { 1275 final int action = ev.getAction(); 1276 if (action == MotionEvent.ACTION_OUTSIDE 1277 || (action == MotionEvent.ACTION_DOWN 1278 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1279 mHandler.removeMessages(mMsg); 1280 mHandler.sendEmptyMessage(mMsg); 1281 return true; 1282 } 1283 return false; 1284 } 1285 } 1286 1287 protected void workAroundBadLayerDrawableOpacity(View v) { 1288 } 1289 1290 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 1291 return inflateViews(entry, parent, false); 1292 } 1293 1294 protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { 1295 return inflateViews(entry, parent, true); 1296 } 1297 1298 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { 1299 PackageManager pmUser = getPackageManagerForUser( 1300 entry.notification.getUser().getIdentifier()); 1301 1302 int maxHeight = mRowMaxHeight; 1303 final StatusBarNotification sbn = entry.notification; 1304 RemoteViews contentView = sbn.getNotification().contentView; 1305 RemoteViews bigContentView = sbn.getNotification().bigContentView; 1306 1307 if (isHeadsUp) { 1308 maxHeight = 1309 mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height); 1310 bigContentView = sbn.getNotification().headsUpContentView; 1311 } 1312 1313 if (contentView == null) { 1314 return false; 1315 } 1316 1317 if (DEBUG) { 1318 Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion); 1319 } 1320 1321 Notification publicNotification = sbn.getNotification().publicVersion; 1322 1323 ExpandableNotificationRow row; 1324 1325 // Stash away previous user expansion state so we can restore it at 1326 // the end. 1327 boolean hasUserChangedExpansion = false; 1328 boolean userExpanded = false; 1329 boolean userLocked = false; 1330 1331 if (entry.row != null) { 1332 row = entry.row; 1333 hasUserChangedExpansion = row.hasUserChangedExpansion(); 1334 userExpanded = row.isUserExpanded(); 1335 userLocked = row.isUserLocked(); 1336 entry.reset(); 1337 if (hasUserChangedExpansion) { 1338 row.setUserExpanded(userExpanded); 1339 } 1340 } else { 1341 // create the row view 1342 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( 1343 Context.LAYOUT_INFLATER_SERVICE); 1344 row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row, 1345 parent, false); 1346 row.setExpansionLogger(this, entry.notification.getKey()); 1347 } 1348 1349 workAroundBadLayerDrawableOpacity(row); 1350 View vetoButton = updateNotificationVetoButton(row, sbn); 1351 vetoButton.setContentDescription(mContext.getString( 1352 R.string.accessibility_remove_notification)); 1353 1354 // NB: the large icon is now handled entirely by the template 1355 1356 // bind the click event to the content area 1357 NotificationContentView expanded = 1358 (NotificationContentView) row.findViewById(R.id.expanded); 1359 NotificationContentView expandedPublic = 1360 (NotificationContentView) row.findViewById(R.id.expandedPublic); 1361 1362 row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1363 1364 PendingIntent contentIntent = sbn.getNotification().contentIntent; 1365 if (contentIntent != null) { 1366 final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(), 1367 isHeadsUp); 1368 row.setOnClickListener(listener); 1369 } else { 1370 row.setOnClickListener(null); 1371 } 1372 1373 // set up the adaptive layout 1374 View contentViewLocal = null; 1375 View bigContentViewLocal = null; 1376 try { 1377 contentViewLocal = contentView.apply(mContext, expanded, 1378 mOnClickHandler); 1379 if (bigContentView != null) { 1380 bigContentViewLocal = bigContentView.apply(mContext, expanded, 1381 mOnClickHandler); 1382 } 1383 } 1384 catch (RuntimeException e) { 1385 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 1386 Log.e(TAG, "couldn't inflate view for notification " + ident, e); 1387 return false; 1388 } 1389 1390 if (contentViewLocal != null) { 1391 contentViewLocal.setIsRootNamespace(true); 1392 expanded.setContractedChild(contentViewLocal); 1393 } 1394 if (bigContentViewLocal != null) { 1395 bigContentViewLocal.setIsRootNamespace(true); 1396 expanded.setExpandedChild(bigContentViewLocal); 1397 } 1398 1399 // now the public version 1400 View publicViewLocal = null; 1401 if (publicNotification != null) { 1402 try { 1403 publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic, 1404 mOnClickHandler); 1405 1406 if (publicViewLocal != null) { 1407 publicViewLocal.setIsRootNamespace(true); 1408 expandedPublic.setContractedChild(publicViewLocal); 1409 } 1410 } 1411 catch (RuntimeException e) { 1412 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 1413 Log.e(TAG, "couldn't inflate public view for notification " + ident, e); 1414 publicViewLocal = null; 1415 } 1416 } 1417 1418 // Extract target SDK version. 1419 try { 1420 ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); 1421 entry.targetSdk = info.targetSdkVersion; 1422 } catch (NameNotFoundException ex) { 1423 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 1424 } 1425 1426 if (publicViewLocal == null) { 1427 // Add a basic notification template 1428 publicViewLocal = LayoutInflater.from(mContext).inflate( 1429 R.layout.notification_public_default, 1430 expandedPublic, false); 1431 publicViewLocal.setIsRootNamespace(true); 1432 expandedPublic.setContractedChild(publicViewLocal); 1433 1434 final TextView title = (TextView) publicViewLocal.findViewById(R.id.title); 1435 try { 1436 title.setText(pmUser.getApplicationLabel( 1437 pmUser.getApplicationInfo(entry.notification.getPackageName(), 0))); 1438 } catch (NameNotFoundException e) { 1439 title.setText(entry.notification.getPackageName()); 1440 } 1441 1442 final ImageView icon = (ImageView) publicViewLocal.findViewById(R.id.icon); 1443 final ImageView profileBadge = (ImageView) publicViewLocal.findViewById( 1444 R.id.profile_badge_line3); 1445 1446 final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(), 1447 entry.notification.getUser(), 1448 entry.notification.getNotification().icon, 1449 entry.notification.getNotification().iconLevel, 1450 entry.notification.getNotification().number, 1451 entry.notification.getNotification().tickerText); 1452 1453 Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic); 1454 icon.setImageDrawable(iconDrawable); 1455 if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP 1456 || mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) { 1457 icon.setBackgroundResource( 1458 com.android.internal.R.drawable.notification_icon_legacy_bg); 1459 int padding = mContext.getResources().getDimensionPixelSize( 1460 com.android.internal.R.dimen.notification_large_icon_circle_padding); 1461 icon.setPadding(padding, padding, padding, padding); 1462 if (sbn.getNotification().color != Notification.COLOR_DEFAULT) { 1463 icon.getBackground().setColorFilter( 1464 sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP); 1465 } 1466 } 1467 1468 if (profileBadge != null) { 1469 Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity( 1470 entry.notification.getUser(), 0); 1471 if (profileDrawable != null) { 1472 profileBadge.setImageDrawable(profileDrawable); 1473 profileBadge.setVisibility(View.VISIBLE); 1474 } else { 1475 profileBadge.setVisibility(View.GONE); 1476 } 1477 } 1478 1479 final View privateTime = contentViewLocal.findViewById(com.android.internal.R.id.time); 1480 final DateTimeView time = (DateTimeView) publicViewLocal.findViewById(R.id.time); 1481 if (privateTime != null && privateTime.getVisibility() == View.VISIBLE) { 1482 time.setVisibility(View.VISIBLE); 1483 time.setTime(entry.notification.getNotification().when); 1484 } 1485 1486 final TextView text = (TextView) publicViewLocal.findViewById(R.id.text); 1487 if (text != null) { 1488 text.setText(R.string.notification_hidden_text); 1489 text.setTextAppearance(mContext, 1490 R.style.TextAppearance_Material_Notification_Parenthetical); 1491 } 1492 1493 int topPadding = Notification.Builder.calculateTopPadding(mContext, 1494 false /* hasThreeLines */, 1495 mContext.getResources().getConfiguration().fontScale); 1496 title.setPadding(0, topPadding, 0, 0); 1497 1498 entry.autoRedacted = true; 1499 } 1500 1501 if (MULTIUSER_DEBUG) { 1502 TextView debug = (TextView) row.findViewById(R.id.debug_info); 1503 if (debug != null) { 1504 debug.setVisibility(View.VISIBLE); 1505 debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId()); 1506 } 1507 } 1508 entry.row = row; 1509 entry.row.setHeightRange(mRowMinHeight, maxHeight); 1510 entry.row.setOnActivatedListener(this); 1511 entry.expanded = contentViewLocal; 1512 entry.expandedPublic = publicViewLocal; 1513 entry.setBigContentView(bigContentViewLocal); 1514 1515 applyColorsAndBackgrounds(sbn, entry); 1516 1517 // Restore previous flags. 1518 if (hasUserChangedExpansion) { 1519 // Note: setUserExpanded() conveniently ignores calls with 1520 // userExpanded=true if !isExpandable(). 1521 row.setUserExpanded(userExpanded); 1522 } 1523 row.setUserLocked(userLocked); 1524 row.setStatusBarNotification(entry.notification); 1525 return true; 1526 } 1527 1528 public NotificationClicker makeClicker(PendingIntent intent, String notificationKey, 1529 boolean forHun) { 1530 return new NotificationClicker(intent, notificationKey, forHun); 1531 } 1532 1533 protected class NotificationClicker implements View.OnClickListener { 1534 private PendingIntent mIntent; 1535 private final String mNotificationKey; 1536 private boolean mIsHeadsUp; 1537 1538 public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) { 1539 mIntent = intent; 1540 mNotificationKey = notificationKey; 1541 mIsHeadsUp = forHun; 1542 } 1543 1544 public void onClick(final View v) { 1545 if (NOTIFICATION_CLICK_DEBUG) { 1546 Log.d(TAG, "Clicked on content of " + mNotificationKey); 1547 } 1548 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 1549 final boolean afterKeyguardGone = mIntent.isActivity() 1550 && PreviewInflater.wouldLaunchResolverActivity(mContext, mIntent.getIntent(), 1551 mCurrentUserId); 1552 dismissKeyguardThenExecute(new OnDismissAction() { 1553 public boolean onDismiss() { 1554 if (mIsHeadsUp) { 1555 mHeadsUpNotificationView.clear(); 1556 } 1557 new Thread() { 1558 @Override 1559 public void run() { 1560 try { 1561 if (keyguardShowing && !afterKeyguardGone) { 1562 ActivityManagerNative.getDefault() 1563 .keyguardWaitingForActivityDrawn(); 1564 } 1565 1566 // The intent we are sending is for the application, which 1567 // won't have permission to immediately start an activity after 1568 // the user switches to home. We know it is safe to do at this 1569 // point, so make sure new activity switches are now allowed. 1570 ActivityManagerNative.getDefault().resumeAppSwitches(); 1571 } catch (RemoteException e) { 1572 } 1573 1574 if (mIntent != null) { 1575 try { 1576 mIntent.send(); 1577 } catch (PendingIntent.CanceledException e) { 1578 // the stack trace isn't very helpful here. 1579 // Just log the exception message. 1580 Log.w(TAG, "Sending contentIntent failed: " + e); 1581 1582 // TODO: Dismiss Keyguard. 1583 } 1584 if (mIntent.isActivity()) { 1585 overrideActivityPendingAppTransition(keyguardShowing 1586 && !afterKeyguardGone); 1587 } 1588 } 1589 1590 try { 1591 mBarService.onNotificationClick(mNotificationKey); 1592 } catch (RemoteException ex) { 1593 // system process is dead if we're here. 1594 } 1595 } 1596 }.start(); 1597 1598 // close the shade if it was open 1599 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, 1600 true /* force */); 1601 visibilityChanged(false); 1602 1603 return mIntent != null && mIntent.isActivity(); 1604 } 1605 }, afterKeyguardGone); 1606 } 1607 } 1608 1609 public void animateCollapsePanels(int flags, boolean force) { 1610 } 1611 1612 public void overrideActivityPendingAppTransition(boolean keyguardShowing) { 1613 if (keyguardShowing) { 1614 try { 1615 mWindowManagerService.overridePendingAppTransition(null, 0, 0, null); 1616 } catch (RemoteException e) { 1617 Log.w(TAG, "Error overriding app transition: " + e); 1618 } 1619 } 1620 } 1621 1622 protected void visibilityChanged(boolean visible) { 1623 if (mVisible != visible) { 1624 mVisible = visible; 1625 if (!visible) { 1626 dismissPopups(); 1627 } 1628 } 1629 updateVisibleToUser(); 1630 } 1631 1632 protected void updateVisibleToUser() { 1633 boolean oldVisibleToUser = mVisibleToUser; 1634 mVisibleToUser = mVisible && mScreenOnFromKeyguard; 1635 1636 if (oldVisibleToUser != mVisibleToUser) { 1637 handleVisibleToUserChanged(mVisibleToUser); 1638 } 1639 } 1640 1641 /** 1642 * The LEDs are turned off when the notification panel is shown, even just a little bit. 1643 * This was added last-minute and is inconsistent with the way the rest of the notifications 1644 * are handled, because the notification isn't really cancelled. The lights are just 1645 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1646 * this is what he wants. (see bug 1131461) 1647 */ 1648 protected void handleVisibleToUserChanged(boolean visibleToUser) { 1649 try { 1650 if (visibleToUser) { 1651 mBarService.onPanelRevealed(); 1652 } else { 1653 mBarService.onPanelHidden(); 1654 } 1655 } catch (RemoteException ex) { 1656 // Won't fail unless the world has ended. 1657 } 1658 } 1659 1660 /** 1661 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 1662 * about the failure. 1663 * 1664 * WARNING: this will call back into us. Don't hold any locks. 1665 */ 1666 void handleNotificationError(StatusBarNotification n, String message) { 1667 removeNotification(n.getKey(), null); 1668 try { 1669 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), 1670 n.getInitialPid(), message, n.getUserId()); 1671 } catch (RemoteException ex) { 1672 // The end is nigh. 1673 } 1674 } 1675 1676 protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { 1677 NotificationData.Entry entry = mNotificationData.remove(key, ranking); 1678 if (entry == null) { 1679 Log.w(TAG, "removeNotification for unknown key: " + key); 1680 return null; 1681 } 1682 updateNotifications(); 1683 return entry.notification; 1684 } 1685 1686 protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) { 1687 if (DEBUG) { 1688 Log.d(TAG, "createNotificationViews(notification=" + sbn); 1689 } 1690 // Construct the icon. 1691 Notification n = sbn.getNotification(); 1692 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1693 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n); 1694 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1695 1696 final StatusBarIcon ic = new StatusBarIcon(sbn.getPackageName(), 1697 sbn.getUser(), 1698 n.icon, 1699 n.iconLevel, 1700 n.number, 1701 n.tickerText); 1702 if (!iconView.set(ic)) { 1703 handleNotificationError(sbn, "Couldn't create icon: " + ic); 1704 return null; 1705 } 1706 // Construct the expanded view. 1707 NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView); 1708 if (!inflateViews(entry, mStackScroller)) { 1709 handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn); 1710 return null; 1711 } 1712 return entry; 1713 } 1714 1715 protected void addNotificationViews(Entry entry, RankingMap ranking) { 1716 if (entry == null) { 1717 return; 1718 } 1719 // Add the expanded view and icon. 1720 mNotificationData.add(entry, ranking); 1721 updateNotifications(); 1722 } 1723 1724 /** 1725 * @return The number of notifications we show on Keyguard. 1726 */ 1727 protected abstract int getMaxKeyguardNotifications(); 1728 1729 /** 1730 * Updates expanded, dimmed and locked states of notification rows. 1731 */ 1732 protected void updateRowStates() { 1733 int maxKeyguardNotifications = getMaxKeyguardNotifications(); 1734 mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); 1735 1736 ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); 1737 final int N = activeNotifications.size(); 1738 1739 int visibleNotifications = 0; 1740 boolean onKeyguard = mState == StatusBarState.KEYGUARD; 1741 for (int i = 0; i < N; i++) { 1742 NotificationData.Entry entry = activeNotifications.get(i); 1743 if (onKeyguard) { 1744 entry.row.setExpansionDisabled(true); 1745 } else { 1746 entry.row.setExpansionDisabled(false); 1747 if (!entry.row.isUserLocked()) { 1748 boolean top = (i == 0); 1749 entry.row.setSystemExpanded(top); 1750 } 1751 } 1752 boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); 1753 if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) || 1754 (onKeyguard && (visibleNotifications >= maxKeyguardNotifications 1755 || !showOnKeyguard))) { 1756 entry.row.setVisibility(View.GONE); 1757 if (onKeyguard && showOnKeyguard) { 1758 mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); 1759 } 1760 } else { 1761 boolean wasGone = entry.row.getVisibility() == View.GONE; 1762 entry.row.setVisibility(View.VISIBLE); 1763 if (wasGone) { 1764 // notify the scroller of a child addition 1765 mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */); 1766 } 1767 visibleNotifications++; 1768 } 1769 } 1770 1771 if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { 1772 mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE); 1773 } else { 1774 mKeyguardIconOverflowContainer.setVisibility(View.GONE); 1775 } 1776 1777 mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, 1778 mStackScroller.getChildCount() - 3); 1779 mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2); 1780 mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1); 1781 } 1782 1783 private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { 1784 return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey()); 1785 } 1786 1787 protected void setZenMode(int mode) { 1788 if (!isDeviceProvisioned()) return; 1789 mZenMode = mode; 1790 updateNotifications(); 1791 } 1792 1793 // extended in PhoneStatusBar 1794 protected void setShowLockscreenNotifications(boolean show) { 1795 mShowLockscreenNotifications = show; 1796 } 1797 1798 private void updateLockscreenNotificationSetting() { 1799 final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(), 1800 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1801 1, 1802 mCurrentUserId) != 0; 1803 final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( 1804 null /* admin */, mCurrentUserId); 1805 final boolean allowedByDpm = (dpmFlags 1806 & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; 1807 setShowLockscreenNotifications(show && allowedByDpm); 1808 } 1809 1810 protected abstract void haltTicker(); 1811 protected abstract void setAreThereNotifications(); 1812 protected abstract void updateNotifications(); 1813 protected abstract void tick(StatusBarNotification n, boolean firstTime); 1814 protected abstract void updateExpandedViewPos(int expandedPosition); 1815 protected abstract boolean shouldDisableNavbarGestures(); 1816 1817 public abstract void addNotification(StatusBarNotification notification, 1818 RankingMap ranking); 1819 protected abstract void updateNotificationRanking(RankingMap ranking); 1820 public abstract void removeNotification(String key, RankingMap ranking); 1821 1822 public void updateNotification(StatusBarNotification notification, RankingMap ranking) { 1823 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); 1824 1825 final String key = notification.getKey(); 1826 boolean wasHeadsUp = isHeadsUp(key); 1827 Entry oldEntry; 1828 if (wasHeadsUp) { 1829 oldEntry = mHeadsUpNotificationView.getEntry(); 1830 } else { 1831 oldEntry = mNotificationData.get(key); 1832 } 1833 if (oldEntry == null) { 1834 return; 1835 } 1836 1837 final StatusBarNotification oldNotification = oldEntry.notification; 1838 1839 // XXX: modify when we do something more intelligent with the two content views 1840 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 1841 Notification n = notification.getNotification(); 1842 final RemoteViews contentView = n.contentView; 1843 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 1844 final RemoteViews bigContentView = n.bigContentView; 1845 final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView; 1846 final RemoteViews headsUpContentView = n.headsUpContentView; 1847 final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; 1848 final RemoteViews oldPublicContentView = oldPublicNotification != null 1849 ? oldPublicNotification.contentView : null; 1850 final Notification publicNotification = n.publicVersion; 1851 final RemoteViews publicContentView = publicNotification != null 1852 ? publicNotification.contentView : null; 1853 1854 if (DEBUG) { 1855 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 1856 + " ongoing=" + oldNotification.isOngoing() 1857 + " expanded=" + oldEntry.expanded 1858 + " contentView=" + oldContentView 1859 + " bigContentView=" + oldBigContentView 1860 + " publicView=" + oldPublicContentView 1861 + " rowParent=" + oldEntry.row.getParent()); 1862 Log.d(TAG, "new notification: when=" + n.when 1863 + " ongoing=" + oldNotification.isOngoing() 1864 + " contentView=" + contentView 1865 + " bigContentView=" + bigContentView 1866 + " publicView=" + publicContentView); 1867 } 1868 1869 // Can we just reapply the RemoteViews in place? 1870 1871 // 1U is never null 1872 boolean contentsUnchanged = oldEntry.expanded != null 1873 && contentView.getPackage() != null 1874 && oldContentView.getPackage() != null 1875 && oldContentView.getPackage().equals(contentView.getPackage()) 1876 && oldContentView.getLayoutId() == contentView.getLayoutId(); 1877 // large view may be null 1878 boolean bigContentsUnchanged = 1879 (oldEntry.getBigContentView() == null && bigContentView == null) 1880 || ((oldEntry.getBigContentView() != null && bigContentView != null) 1881 && bigContentView.getPackage() != null 1882 && oldBigContentView.getPackage() != null 1883 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 1884 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 1885 boolean headsUpContentsUnchanged = 1886 (oldHeadsUpContentView == null && headsUpContentView == null) 1887 || ((oldHeadsUpContentView != null && headsUpContentView != null) 1888 && headsUpContentView.getPackage() != null 1889 && oldHeadsUpContentView.getPackage() != null 1890 && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) 1891 && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); 1892 boolean publicUnchanged = 1893 (oldPublicContentView == null && publicContentView == null) 1894 || ((oldPublicContentView != null && publicContentView != null) 1895 && publicContentView.getPackage() != null 1896 && oldPublicContentView.getPackage() != null 1897 && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) 1898 && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); 1899 boolean updateTicker = n.tickerText != null 1900 && !TextUtils.equals(n.tickerText, 1901 oldEntry.notification.getNotification().tickerText); 1902 1903 final boolean shouldInterrupt = shouldInterrupt(notification); 1904 final boolean alertAgain = alertAgain(oldEntry, n); 1905 boolean updateSuccessful = false; 1906 if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged 1907 && publicUnchanged) { 1908 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); 1909 oldEntry.notification = notification; 1910 try { 1911 if (oldEntry.icon != null) { 1912 // Update the icon 1913 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1914 notification.getUser(), 1915 n.icon, 1916 n.iconLevel, 1917 n.number, 1918 n.tickerText); 1919 oldEntry.icon.setNotification(n); 1920 if (!oldEntry.icon.set(ic)) { 1921 handleNotificationError(notification, "Couldn't update icon: " + ic); 1922 return; 1923 } 1924 } 1925 1926 if (wasHeadsUp) { 1927 if (shouldInterrupt) { 1928 updateHeadsUpViews(oldEntry, notification); 1929 if (alertAgain) { 1930 resetHeadsUpDecayTimer(); 1931 } 1932 } else { 1933 // we updated the notification above, so release to build a new shade entry 1934 mHeadsUpNotificationView.releaseAndClose(); 1935 return; 1936 } 1937 } else { 1938 if (shouldInterrupt && alertAgain) { 1939 removeNotificationViews(key, ranking); 1940 addNotification(notification, ranking); //this will pop the headsup 1941 } else { 1942 updateNotificationViews(oldEntry, notification); 1943 } 1944 } 1945 mNotificationData.updateRanking(ranking); 1946 updateNotifications(); 1947 updateSuccessful = true; 1948 } 1949 catch (RuntimeException e) { 1950 // It failed to add cleanly. Log, and remove the view from the panel. 1951 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1952 } 1953 } 1954 if (!updateSuccessful) { 1955 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); 1956 if (wasHeadsUp) { 1957 if (shouldInterrupt) { 1958 if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key); 1959 Entry newEntry = new Entry(notification, null); 1960 ViewGroup holder = mHeadsUpNotificationView.getHolder(); 1961 if (inflateViewsForHeadsUp(newEntry, holder)) { 1962 mHeadsUpNotificationView.showNotification(newEntry); 1963 if (alertAgain) { 1964 resetHeadsUpDecayTimer(); 1965 } 1966 } else { 1967 Log.w(TAG, "Couldn't create new updated headsup for package " 1968 + contentView.getPackage()); 1969 } 1970 } else { 1971 if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key); 1972 oldEntry.notification = notification; 1973 mHeadsUpNotificationView.releaseAndClose(); 1974 return; 1975 } 1976 } else { 1977 if (shouldInterrupt && alertAgain) { 1978 if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key); 1979 removeNotificationViews(key, ranking); 1980 addNotification(notification, ranking); //this will pop the headsup 1981 } else { 1982 if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key); 1983 oldEntry.notification = notification; 1984 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1985 notification.getUser(), 1986 n.icon, 1987 n.iconLevel, 1988 n.number, 1989 n.tickerText); 1990 oldEntry.icon.setNotification(n); 1991 oldEntry.icon.set(ic); 1992 inflateViews(oldEntry, mStackScroller, wasHeadsUp); 1993 mNotificationData.updateRanking(ranking); 1994 updateNotifications(); 1995 } 1996 } 1997 } 1998 1999 // Update the veto button accordingly (and as a result, whether this row is 2000 // swipe-dismissable) 2001 updateNotificationVetoButton(oldEntry.row, notification); 2002 2003 // Is this for you? 2004 boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); 2005 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 2006 2007 // Restart the ticker if it's still running 2008 if (updateTicker && isForCurrentUser) { 2009 haltTicker(); 2010 tick(notification, false); 2011 } 2012 2013 // Recalculate the position of the sliding windows and the titles. 2014 setAreThereNotifications(); 2015 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 2016 } 2017 2018 private void updateNotificationViews(NotificationData.Entry entry, 2019 StatusBarNotification notification) { 2020 updateNotificationViews(entry, notification, false); 2021 } 2022 2023 private void updateHeadsUpViews(NotificationData.Entry entry, 2024 StatusBarNotification notification) { 2025 updateNotificationViews(entry, notification, true); 2026 } 2027 2028 private void updateNotificationViews(NotificationData.Entry entry, 2029 StatusBarNotification notification, boolean isHeadsUp) { 2030 final RemoteViews contentView = notification.getNotification().contentView; 2031 final RemoteViews bigContentView = isHeadsUp 2032 ? notification.getNotification().headsUpContentView 2033 : notification.getNotification().bigContentView; 2034 final Notification publicVersion = notification.getNotification().publicVersion; 2035 final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView 2036 : null; 2037 2038 // Reapply the RemoteViews 2039 contentView.reapply(mContext, entry.expanded, mOnClickHandler); 2040 if (bigContentView != null && entry.getBigContentView() != null) { 2041 bigContentView.reapply(mContext, entry.getBigContentView(), 2042 mOnClickHandler); 2043 } 2044 if (publicContentView != null && entry.getPublicContentView() != null) { 2045 publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); 2046 } 2047 // update the contentIntent 2048 final PendingIntent contentIntent = notification.getNotification().contentIntent; 2049 if (contentIntent != null) { 2050 final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(), 2051 isHeadsUp); 2052 entry.row.setOnClickListener(listener); 2053 } else { 2054 entry.row.setOnClickListener(null); 2055 } 2056 entry.row.setStatusBarNotification(notification); 2057 entry.row.notifyContentUpdated(); 2058 entry.row.resetHeight(); 2059 } 2060 2061 protected void notifyHeadsUpScreenOn(boolean screenOn) { 2062 if (!screenOn) { 2063 scheduleHeadsUpEscalation(); 2064 } 2065 } 2066 2067 private boolean alertAgain(Entry oldEntry, Notification newNotification) { 2068 return oldEntry == null || !oldEntry.hasInterrupted() 2069 || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; 2070 } 2071 2072 protected boolean shouldInterrupt(StatusBarNotification sbn) { 2073 if (mNotificationData.shouldFilterOut(sbn)) { 2074 if (DEBUG) { 2075 Log.d(TAG, "Skipping HUN check for " + sbn.getKey() + " since it's filtered out."); 2076 } 2077 return false; 2078 } 2079 2080 if (mHeadsUpNotificationView.isSnoozed(sbn.getPackageName())) { 2081 return false; 2082 } 2083 2084 Notification notification = sbn.getNotification(); 2085 // some predicates to make the boolean logic legible 2086 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 2087 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 2088 || notification.sound != null 2089 || notification.vibrate != null; 2090 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 2091 boolean isFullscreen = notification.fullScreenIntent != null; 2092 boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText); 2093 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 2094 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 2095 boolean accessibilityForcesLaunch = isFullscreen 2096 && mAccessibilityManager.isTouchExplorationEnabled(); 2097 2098 boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker))) 2099 && isAllowed 2100 && !accessibilityForcesLaunch 2101 && mPowerManager.isScreenOn() 2102 && (!mStatusBarKeyguardViewManager.isShowing() 2103 || mStatusBarKeyguardViewManager.isOccluded()) 2104 && !mStatusBarKeyguardViewManager.isInputRestricted(); 2105 try { 2106 interrupt = interrupt && !mDreamManager.isDreaming(); 2107 } catch (RemoteException e) { 2108 Log.d(TAG, "failed to query dream manager", e); 2109 } 2110 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 2111 return interrupt; 2112 } 2113 2114 public void setInteracting(int barWindow, boolean interacting) { 2115 // hook for subclasses 2116 } 2117 2118 public void setBouncerShowing(boolean bouncerShowing) { 2119 mBouncerShowing = bouncerShowing; 2120 } 2121 2122 /** 2123 * @return Whether the security bouncer from Keyguard is showing. 2124 */ 2125 public boolean isBouncerShowing() { 2126 return mBouncerShowing; 2127 } 2128 2129 public void destroy() { 2130 if (mSearchPanelView != null) { 2131 mWindowManager.removeViewImmediate(mSearchPanelView); 2132 } 2133 mContext.unregisterReceiver(mBroadcastReceiver); 2134 try { 2135 mNotificationListener.unregisterAsSystemService(); 2136 } catch (RemoteException e) { 2137 // Ignore. 2138 } 2139 } 2140 2141 /** 2142 * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then 2143 * return PackageManager for mContext 2144 */ 2145 protected PackageManager getPackageManagerForUser(int userId) { 2146 Context contextForUser = mContext; 2147 // UserHandle defines special userId as negative values, e.g. USER_ALL 2148 if (userId >= 0) { 2149 try { 2150 // Create a context for the correct user so if a package isn't installed 2151 // for user 0 we can still load information about the package. 2152 contextForUser = 2153 mContext.createPackageContextAsUser(mContext.getPackageName(), 2154 Context.CONTEXT_RESTRICTED, 2155 new UserHandle(userId)); 2156 } catch (NameNotFoundException e) { 2157 // Shouldn't fail to find the package name for system ui. 2158 } 2159 } 2160 return contextForUser.getPackageManager(); 2161 } 2162 2163 @Override 2164 public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { 2165 try { 2166 mBarService.onNotificationExpansionChanged(key, userAction, expanded); 2167 } catch (RemoteException e) { 2168 // Ignore. 2169 } 2170 } 2171 2172 public boolean isKeyguardSecure() { 2173 return mStatusBarKeyguardViewManager.isSecure(); 2174 } 2175} 2176