BaseStatusBar.java revision 9cbadd3c08a7d7dd3412743dd04aecb16c5a1595
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.app.ActivityManager; 20import android.app.ActivityManagerNative; 21import android.app.Notification; 22import android.app.PendingIntent; 23import android.app.TaskStackBuilder; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.pm.ApplicationInfo; 29import android.content.pm.PackageManager; 30import android.content.pm.PackageManager.NameNotFoundException; 31import android.content.pm.UserInfo; 32import android.content.res.Configuration; 33import android.database.ContentObserver; 34import android.graphics.Rect; 35import android.graphics.drawable.Drawable; 36import android.net.Uri; 37import android.os.Build; 38import android.os.Handler; 39import android.os.IBinder; 40import android.os.Message; 41import android.os.PowerManager; 42import android.os.RemoteException; 43import android.os.ServiceManager; 44import android.os.UserHandle; 45import android.os.UserManager; 46import android.provider.Settings; 47import android.service.dreams.DreamService; 48import android.service.dreams.IDreamManager; 49import android.service.notification.StatusBarNotification; 50import android.text.TextUtils; 51import android.util.Log; 52import android.util.SparseArray; 53import android.util.SparseBooleanArray; 54import android.view.Display; 55import android.view.IWindowManager; 56import android.view.LayoutInflater; 57import android.view.MenuItem; 58import android.view.MotionEvent; 59import android.view.View; 60import android.view.ViewGroup; 61import android.view.ViewGroup.LayoutParams; 62import android.view.WindowManager; 63import android.view.WindowManagerGlobal; 64import android.widget.ImageView; 65import android.widget.LinearLayout; 66import android.widget.PopupMenu; 67import android.widget.RemoteViews; 68import android.widget.TextView; 69 70import com.android.internal.statusbar.IStatusBarService; 71import com.android.internal.statusbar.StatusBarIcon; 72import com.android.internal.statusbar.StatusBarIconList; 73import com.android.internal.util.LegacyNotificationUtil; 74import com.android.systemui.R; 75import com.android.systemui.RecentsComponent; 76import com.android.systemui.SearchPanelView; 77import com.android.systemui.SystemUI; 78import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; 79import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 80 81import java.util.ArrayList; 82import java.util.Locale; 83 84public abstract class BaseStatusBar extends SystemUI implements 85 CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener { 86 public static final String TAG = "StatusBar"; 87 public static final boolean DEBUG = false; 88 public static final boolean MULTIUSER_DEBUG = false; 89 90 protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020; 91 protected static final int MSG_CLOSE_RECENTS_PANEL = 1021; 92 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 93 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 94 protected static final int MSG_OPEN_SEARCH_PANEL = 1024; 95 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025; 96 protected static final int MSG_SHOW_HEADS_UP = 1026; 97 protected static final int MSG_HIDE_HEADS_UP = 1027; 98 protected static final int MSG_ESCALATE_HEADS_UP = 1028; 99 100 protected static final boolean ENABLE_HEADS_UP = true; 101 // scores above this threshold should be displayed in heads up mode. 102 protected static final int INTERRUPTION_THRESHOLD = 10; 103 protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; 104 105 // Should match the value in PhoneWindowManager 106 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 107 108 public static final int EXPANDED_LEAVE_ALONE = -10000; 109 public static final int EXPANDED_FULL_OPEN = -10001; 110 111 protected CommandQueue mCommandQueue; 112 protected IStatusBarService mBarService; 113 protected H mHandler = createHandler(); 114 115 // all notifications 116 protected NotificationData mNotificationData = new NotificationData(); 117 protected NotificationStackScrollLayout mStackScroller; 118 119 protected NotificationData.Entry mInterruptingNotificationEntry; 120 protected long mInterruptingNotificationTime; 121 122 // used to notify status bar for suppressing notification LED 123 protected boolean mPanelSlightlyVisible; 124 125 // Search panel 126 protected SearchPanelView mSearchPanelView; 127 128 protected PopupMenu mNotificationBlamePopup; 129 130 protected int mCurrentUserId = 0; 131 final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); 132 133 protected int mLayoutDirection = -1; // invalid 134 private Locale mLocale; 135 protected boolean mUseHeadsUp = false; 136 protected boolean mHeadsUpTicker = false; 137 138 protected IDreamManager mDreamManager; 139 PowerManager mPowerManager; 140 protected int mRowMinHeight; 141 protected int mRowMaxHeight; 142 143 // public mode, private notifications, etc 144 private boolean mLockscreenPublicMode = false; 145 private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); 146 private LegacyNotificationUtil mLegacyNotificationUtil = LegacyNotificationUtil.getInstance(); 147 148 private UserManager mUserManager; 149 150 // UI-specific methods 151 152 /** 153 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 154 * and add them to the window manager. 155 */ 156 protected abstract void createAndAddWindows(); 157 158 protected WindowManager mWindowManager; 159 protected IWindowManager mWindowManagerService; 160 protected abstract void refreshLayout(int layoutDirection); 161 162 protected Display mDisplay; 163 164 private boolean mDeviceProvisioned = false; 165 166 private RecentsComponent mRecents; 167 168 protected int mZenMode; 169 170 /** 171 * The {@link StatusBarState} of the status bar. 172 */ 173 protected int mState; 174 protected boolean mBouncerShowing; 175 176 protected NotificationOverflowContainer mKeyguardIconOverflowContainer; 177 178 public boolean isDeviceProvisioned() { 179 return mDeviceProvisioned; 180 } 181 182 protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 183 @Override 184 public void onChange(boolean selfChange) { 185 final boolean provisioned = 0 != Settings.Global.getInt( 186 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); 187 if (provisioned != mDeviceProvisioned) { 188 mDeviceProvisioned = provisioned; 189 updateNotificationIcons(); 190 } 191 final int mode = Settings.Global.getInt(mContext.getContentResolver(), 192 Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); 193 setZenMode(mode); 194 } 195 }; 196 197 private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) { 198 @Override 199 public void onChange(boolean selfChange) { 200 // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 201 // so we just dump our cache ... 202 mUsersAllowingPrivateNotifications.clear(); 203 // ... and refresh all the notifications 204 updateNotificationIcons(); 205 } 206 }; 207 208 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { 209 @Override 210 public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { 211 if (DEBUG) { 212 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); 213 } 214 final boolean isActivity = pendingIntent.isActivity(); 215 if (isActivity) { 216 try { 217 // The intent we are sending is for the application, which 218 // won't have permission to immediately start an activity after 219 // the user switches to home. We know it is safe to do at this 220 // point, so make sure new activity switches are now allowed. 221 ActivityManagerNative.getDefault().resumeAppSwitches(); 222 // Also, notifications can be launched from the lock screen, 223 // so dismiss the lock screen when the activity starts. 224 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 225 } catch (RemoteException e) { 226 } 227 } 228 229 boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent); 230 231 if (isActivity && handled) { 232 // close the shade if it was open 233 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 234 visibilityChanged(false); 235 } 236 return handled; 237 } 238 }; 239 240 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 241 @Override 242 public void onReceive(Context context, Intent intent) { 243 String action = intent.getAction(); 244 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 245 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 246 updateCurrentProfilesCache(); 247 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); 248 userSwitched(mCurrentUserId); 249 } else if (Intent.ACTION_USER_ADDED.equals(action)) { 250 updateCurrentProfilesCache(); 251 } 252 } 253 }; 254 255 private void updateCurrentProfilesCache() { 256 synchronized (mCurrentProfiles) { 257 mCurrentProfiles.clear(); 258 if (mUserManager != null) { 259 for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { 260 mCurrentProfiles.put(user.id, user); 261 } 262 } 263 } 264 } 265 266 public void start() { 267 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 268 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 269 mDisplay = mWindowManager.getDefaultDisplay(); 270 271 mDreamManager = IDreamManager.Stub.asInterface( 272 ServiceManager.checkService(DreamService.DREAM_SERVICE)); 273 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 274 275 mSettingsObserver.onChange(false); // set up 276 mContext.getContentResolver().registerContentObserver( 277 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, 278 mSettingsObserver); 279 mContext.getContentResolver().registerContentObserver( 280 Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, 281 mSettingsObserver); 282 283 mContext.getContentResolver().registerContentObserver( 284 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), 285 true, 286 mLockscreenSettingsObserver, 287 UserHandle.USER_ALL); 288 289 mBarService = IStatusBarService.Stub.asInterface( 290 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 291 292 mRecents = getComponent(RecentsComponent.class); 293 294 mLocale = mContext.getResources().getConfiguration().locale; 295 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 296 297 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 298 299 // Connect in to the status bar manager service 300 StatusBarIconList iconList = new StatusBarIconList(); 301 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); 302 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 303 mCommandQueue = new CommandQueue(this, iconList); 304 305 int[] switches = new int[7]; 306 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 307 try { 308 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, 309 switches, binders); 310 } catch (RemoteException ex) { 311 // If the system process isn't there we're doomed anyway. 312 } 313 314 createAndAddWindows(); 315 316 disable(switches[0]); 317 setSystemUiVisibility(switches[1], 0xffffffff); 318 topAppWindowChanged(switches[2] != 0); 319 // StatusBarManagerService has a back up of IME token and it's restored here. 320 setImeWindowStatus(binders.get(0), switches[3], switches[4]); 321 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 322 323 // Set up the initial icon state 324 int N = iconList.size(); 325 int viewIndex = 0; 326 for (int i=0; i<N; i++) { 327 StatusBarIcon icon = iconList.getIcon(i); 328 if (icon != null) { 329 addIcon(iconList.getSlot(i), i, viewIndex, icon); 330 viewIndex++; 331 } 332 } 333 334 // Set up the initial notification state 335 N = notificationKeys.size(); 336 if (N == notifications.size()) { 337 for (int i=0; i<N; i++) { 338 addNotification(notificationKeys.get(i), notifications.get(i)); 339 } 340 } else { 341 Log.wtf(TAG, "Notification list length mismatch: keys=" + N 342 + " notifications=" + notifications.size()); 343 } 344 345 if (DEBUG) { 346 Log.d(TAG, String.format( 347 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 348 iconList.size(), 349 switches[0], 350 switches[1], 351 switches[2], 352 switches[3] 353 )); 354 } 355 356 mCurrentUserId = ActivityManager.getCurrentUser(); 357 358 IntentFilter filter = new IntentFilter(); 359 filter.addAction(Intent.ACTION_USER_SWITCHED); 360 filter.addAction(Intent.ACTION_USER_ADDED); 361 mContext.registerReceiver(mBroadcastReceiver, filter); 362 363 updateCurrentProfilesCache(); 364 } 365 366 public void userSwitched(int newUserId) { 367 // should be overridden 368 } 369 370 public boolean notificationIsForCurrentProfiles(StatusBarNotification n) { 371 final int thisUserId = mCurrentUserId; 372 final int notificationUserId = n.getUserId(); 373 if (DEBUG && MULTIUSER_DEBUG) { 374 Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", 375 n, thisUserId, notificationUserId)); 376 } 377 synchronized (mCurrentProfiles) { 378 return notificationUserId == UserHandle.USER_ALL 379 || mCurrentProfiles.get(notificationUserId) != null; 380 } 381 } 382 383 @Override 384 protected void onConfigurationChanged(Configuration newConfig) { 385 final Locale locale = mContext.getResources().getConfiguration().locale; 386 final int ld = TextUtils.getLayoutDirectionFromLocale(locale); 387 if (! locale.equals(mLocale) || ld != mLayoutDirection) { 388 if (DEBUG) { 389 Log.v(TAG, String.format( 390 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, 391 locale, ld)); 392 } 393 mLocale = locale; 394 mLayoutDirection = ld; 395 refreshLayout(ld); 396 } 397 } 398 399 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 400 View vetoButton = row.findViewById(R.id.veto); 401 if (n.isClearable() || (mInterruptingNotificationEntry != null 402 && mInterruptingNotificationEntry.row == row)) { 403 final String _pkg = n.getPackageName(); 404 final String _tag = n.getTag(); 405 final int _id = n.getId(); 406 final int _userId = n.getUserId(); 407 vetoButton.setOnClickListener(new View.OnClickListener() { 408 public void onClick(View v) { 409 // Accessibility feedback 410 v.announceForAccessibility( 411 mContext.getString(R.string.accessibility_notification_dismissed)); 412 try { 413 mBarService.onNotificationClear(_pkg, _tag, _id, _userId); 414 415 } catch (RemoteException ex) { 416 // system process is dead if we're here. 417 } 418 } 419 }); 420 vetoButton.setVisibility(View.VISIBLE); 421 } else { 422 vetoButton.setVisibility(View.GONE); 423 } 424 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 425 return vetoButton; 426 } 427 428 429 protected void applyLegacyRowBackground(StatusBarNotification sbn, 430 NotificationData.Entry entry) { 431 if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) { 432 int version = 0; 433 try { 434 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0); 435 version = info.targetSdkVersion; 436 } catch (NameNotFoundException ex) { 437 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 438 } 439 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 440 entry.row.setBackgroundResource(R.drawable.notification_row_legacy_bg); 441 } else if (version < Build.VERSION_CODES.L) { 442 entry.row.setBackgroundResourceIds( 443 com.android.internal.R.drawable.notification_bg, 444 com.android.internal.R.drawable.notification_bg_dim); 445 } 446 } 447 } 448 449 private void startApplicationDetailsActivity(String packageName) { 450 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 451 Uri.fromParts("package", packageName, null)); 452 intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); 453 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities( 454 null, UserHandle.CURRENT); 455 } 456 457 protected View.OnLongClickListener getNotificationLongClicker() { 458 return new View.OnLongClickListener() { 459 @Override 460 public boolean onLongClick(View v) { 461 final String packageNameF = (String) v.getTag(); 462 if (packageNameF == null) return false; 463 if (v.getWindowToken() == null) return false; 464 mNotificationBlamePopup = new PopupMenu(mContext, v); 465 mNotificationBlamePopup.getMenuInflater().inflate( 466 R.menu.notification_popup_menu, 467 mNotificationBlamePopup.getMenu()); 468 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 469 public boolean onMenuItemClick(MenuItem item) { 470 if (item.getItemId() == R.id.notification_inspect_item) { 471 startApplicationDetailsActivity(packageNameF); 472 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 473 } else { 474 return false; 475 } 476 return true; 477 } 478 }); 479 mNotificationBlamePopup.show(); 480 481 return true; 482 } 483 }; 484 } 485 486 public void dismissPopups() { 487 if (mNotificationBlamePopup != null) { 488 mNotificationBlamePopup.dismiss(); 489 mNotificationBlamePopup = null; 490 } 491 } 492 493 public void onHeadsUpDismissed() { 494 } 495 496 @Override 497 public void toggleRecentApps() { 498 int msg = MSG_TOGGLE_RECENTS_PANEL; 499 mHandler.removeMessages(msg); 500 mHandler.sendEmptyMessage(msg); 501 } 502 503 @Override 504 public void preloadRecentApps() { 505 int msg = MSG_PRELOAD_RECENT_APPS; 506 mHandler.removeMessages(msg); 507 mHandler.sendEmptyMessage(msg); 508 } 509 510 @Override 511 public void cancelPreloadRecentApps() { 512 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 513 mHandler.removeMessages(msg); 514 mHandler.sendEmptyMessage(msg); 515 } 516 517 @Override 518 public void showSearchPanel() { 519 int msg = MSG_OPEN_SEARCH_PANEL; 520 mHandler.removeMessages(msg); 521 mHandler.sendEmptyMessage(msg); 522 } 523 524 @Override 525 public void hideSearchPanel() { 526 int msg = MSG_CLOSE_SEARCH_PANEL; 527 mHandler.removeMessages(msg); 528 mHandler.sendEmptyMessage(msg); 529 } 530 531 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 532 LayoutParams layoutParams); 533 534 protected void updateSearchPanel() { 535 // Search Panel 536 boolean visible = false; 537 if (mSearchPanelView != null) { 538 visible = mSearchPanelView.isShowing(); 539 mWindowManager.removeView(mSearchPanelView); 540 } 541 542 // Provide SearchPanel with a temporary parent to allow layout params to work. 543 LinearLayout tmpRoot = new LinearLayout(mContext); 544 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 545 R.layout.status_bar_search_panel, tmpRoot, false); 546 mSearchPanelView.setOnTouchListener( 547 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 548 mSearchPanelView.setVisibility(View.GONE); 549 550 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 551 552 mWindowManager.addView(mSearchPanelView, lp); 553 mSearchPanelView.setBar(this); 554 if (visible) { 555 mSearchPanelView.show(true, false); 556 } 557 } 558 559 protected H createHandler() { 560 return new H(); 561 } 562 563 static void sendCloseSystemWindows(Context context, String reason) { 564 if (ActivityManagerNative.isSystemReady()) { 565 try { 566 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 567 } catch (RemoteException e) { 568 } 569 } 570 } 571 572 protected abstract View getStatusBarView(); 573 574 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 575 // additional optimization when we have software system buttons - start loading the recent 576 // tasks on touch down 577 @Override 578 public boolean onTouch(View v, MotionEvent event) { 579 int action = event.getAction() & MotionEvent.ACTION_MASK; 580 if (action == MotionEvent.ACTION_DOWN) { 581 preloadRecentTasksList(); 582 } else if (action == MotionEvent.ACTION_CANCEL) { 583 cancelPreloadingRecentTasksList(); 584 } else if (action == MotionEvent.ACTION_UP) { 585 if (!v.isPressed()) { 586 cancelPreloadingRecentTasksList(); 587 } 588 589 } 590 return false; 591 } 592 }; 593 594 protected void toggleRecentsActivity() { 595 if (mRecents != null) { 596 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 597 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView()); 598 } 599 } 600 601 protected void preloadRecentTasksList() { 602 if (mRecents != null) { 603 mRecents.preloadRecentTasksList(); 604 } 605 } 606 607 protected void cancelPreloadingRecentTasksList() { 608 if (mRecents != null) { 609 mRecents.cancelPreloadingRecentTasksList(); 610 } 611 } 612 613 protected void closeRecents() { 614 if (mRecents != null) { 615 mRecents.closeRecents(); 616 } 617 } 618 619 public abstract void resetHeadsUpDecayTimer(); 620 621 /** 622 * Save the current "public" (locked and secure) state of the lockscreen. 623 */ 624 public void setLockscreenPublicMode(boolean publicMode) { 625 mLockscreenPublicMode = publicMode; 626 } 627 628 public boolean isLockscreenPublicMode() { 629 return mLockscreenPublicMode; 630 } 631 632 /** 633 * Has the given user chosen to allow their private (full) notifications to be shown even 634 * when the lockscreen is in "public" (secure & locked) mode? 635 */ 636 public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { 637 if (userHandle == UserHandle.USER_ALL) { 638 return true; 639 } 640 641 if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { 642 final boolean allowed = 0 != Settings.Secure.getIntForUser( 643 mContext.getContentResolver(), 644 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); 645 mUsersAllowingPrivateNotifications.append(userHandle, allowed); 646 return allowed; 647 } 648 649 return mUsersAllowingPrivateNotifications.get(userHandle); 650 } 651 652 protected class H extends Handler { 653 public void handleMessage(Message m) { 654 Intent intent; 655 switch (m.what) { 656 case MSG_TOGGLE_RECENTS_PANEL: 657 toggleRecentsActivity(); 658 break; 659 case MSG_CLOSE_RECENTS_PANEL: 660 closeRecents(); 661 break; 662 case MSG_PRELOAD_RECENT_APPS: 663 preloadRecentTasksList(); 664 break; 665 case MSG_CANCEL_PRELOAD_RECENT_APPS: 666 cancelPreloadingRecentTasksList(); 667 break; 668 case MSG_OPEN_SEARCH_PANEL: 669 if (DEBUG) Log.d(TAG, "opening search panel"); 670 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 671 mSearchPanelView.show(true, true); 672 onShowSearchPanel(); 673 } 674 break; 675 case MSG_CLOSE_SEARCH_PANEL: 676 if (DEBUG) Log.d(TAG, "closing search panel"); 677 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 678 mSearchPanelView.show(false, true); 679 onHideSearchPanel(); 680 } 681 break; 682 } 683 } 684 } 685 686 public class TouchOutsideListener implements View.OnTouchListener { 687 private int mMsg; 688 private StatusBarPanel mPanel; 689 690 public TouchOutsideListener(int msg, StatusBarPanel panel) { 691 mMsg = msg; 692 mPanel = panel; 693 } 694 695 public boolean onTouch(View v, MotionEvent ev) { 696 final int action = ev.getAction(); 697 if (action == MotionEvent.ACTION_OUTSIDE 698 || (action == MotionEvent.ACTION_DOWN 699 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 700 mHandler.removeMessages(mMsg); 701 mHandler.sendEmptyMessage(mMsg); 702 return true; 703 } 704 return false; 705 } 706 } 707 708 protected void workAroundBadLayerDrawableOpacity(View v) { 709 } 710 711 protected void onHideSearchPanel() { 712 } 713 714 protected void onShowSearchPanel() { 715 } 716 717 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 718 return inflateViews(entry, parent, false); 719 } 720 721 public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { 722 return inflateViews(entry, parent, true); 723 } 724 725 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { 726 int minHeight = 727 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 728 int maxHeight = 729 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 730 StatusBarNotification sbn = entry.notification; 731 RemoteViews contentView = sbn.getNotification().contentView; 732 RemoteViews bigContentView = sbn.getNotification().bigContentView; 733 734 if (isHeadsUp) { 735 maxHeight = 736 mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height); 737 bigContentView = sbn.getNotification().headsUpContentView; 738 } 739 740 if (contentView == null) { 741 return false; 742 } 743 744 if (DEBUG) { 745 Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion); 746 } 747 748 Notification publicNotification = sbn.getNotification().publicVersion; 749 750 // create the row view 751 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 752 Context.LAYOUT_INFLATER_SERVICE); 753 ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate( 754 R.layout.status_bar_notification_row, parent, false); 755 756 // for blaming (see SwipeHelper.setLongPressListener) 757 row.setTag(sbn.getPackageName()); 758 759 workAroundBadLayerDrawableOpacity(row); 760 View vetoButton = updateNotificationVetoButton(row, sbn); 761 vetoButton.setContentDescription(mContext.getString( 762 R.string.accessibility_remove_notification)); 763 764 // NB: the large icon is now handled entirely by the template 765 766 // bind the click event to the content area 767 NotificationContentView expanded = 768 (NotificationContentView) row.findViewById(R.id.expanded); 769 NotificationContentView expandedPublic = 770 (NotificationContentView) row.findViewById(R.id.expandedPublic); 771 772 row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 773 774 PendingIntent contentIntent = sbn.getNotification().contentIntent; 775 if (contentIntent != null) { 776 final View.OnClickListener listener = makeClicker(contentIntent, 777 sbn.getPackageName(), sbn.getTag(), sbn.getId(), isHeadsUp, sbn.getUserId()); 778 row.setOnClickListener(listener); 779 } else { 780 row.setOnClickListener(null); 781 } 782 783 // set up the adaptive layout 784 View contentViewLocal = null; 785 View bigContentViewLocal = null; 786 try { 787 contentViewLocal = contentView.apply(mContext, expanded, 788 mOnClickHandler); 789 if (bigContentView != null) { 790 bigContentViewLocal = bigContentView.apply(mContext, expanded, 791 mOnClickHandler); 792 } 793 } 794 catch (RuntimeException e) { 795 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 796 Log.e(TAG, "couldn't inflate view for notification " + ident, e); 797 return false; 798 } 799 800 if (contentViewLocal != null) { 801 contentViewLocal.setIsRootNamespace(true); 802 expanded.setContractedChild(contentViewLocal); 803 } 804 if (bigContentViewLocal != null) { 805 bigContentViewLocal.setIsRootNamespace(true); 806 expanded.setExpandedChild(bigContentViewLocal); 807 } 808 809 PackageManager pm = mContext.getPackageManager(); 810 811 // now the public version 812 View publicViewLocal = null; 813 if (publicNotification != null) { 814 try { 815 publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic, 816 mOnClickHandler); 817 818 if (publicViewLocal != null) { 819 publicViewLocal.setIsRootNamespace(true); 820 expandedPublic.setContractedChild(publicViewLocal); 821 } 822 } 823 catch (RuntimeException e) { 824 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 825 Log.e(TAG, "couldn't inflate public view for notification " + ident, e); 826 publicViewLocal = null; 827 } 828 } 829 830 if (publicViewLocal == null) { 831 // Add a basic notification template 832 publicViewLocal = LayoutInflater.from(mContext).inflate( 833 com.android.internal.R.layout.notification_template_quantum_base, 834 expandedPublic, true); 835 836 final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title); 837 try { 838 title.setText(pm.getApplicationLabel( 839 pm.getApplicationInfo(entry.notification.getPackageName(), 0))); 840 } catch (NameNotFoundException e) { 841 title.setText(entry.notification.getPackageName()); 842 } 843 844 final ImageView icon = (ImageView) publicViewLocal.findViewById(com.android.internal.R.id.icon); 845 846 final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(), 847 entry.notification.getUser(), 848 entry.notification.getNotification().icon, 849 entry.notification.getNotification().iconLevel, 850 entry.notification.getNotification().number, 851 entry.notification.getNotification().tickerText); 852 853 Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic); 854 icon.setImageDrawable(iconDrawable); 855 if (mLegacyNotificationUtil.isGrayscale(iconDrawable)) { 856 icon.setBackgroundResource( 857 com.android.internal.R.drawable.notification_icon_legacy_bg_inset); 858 } 859 860 final TextView text = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.text); 861 text.setText("Unlock your device to see this notification."); 862 863 // TODO: fill out "time" as well 864 } 865 866 row.setDrawingCacheEnabled(true); 867 868 if (MULTIUSER_DEBUG) { 869 TextView debug = (TextView) row.findViewById(R.id.debug_info); 870 if (debug != null) { 871 debug.setVisibility(View.VISIBLE); 872 debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId()); 873 } 874 } 875 entry.row = row; 876 entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight); 877 entry.row.setOnActivatedListener(this); 878 entry.expanded = contentViewLocal; 879 entry.expandedPublic = publicViewLocal; 880 entry.setBigContentView(bigContentViewLocal); 881 882 applyLegacyRowBackground(sbn, entry); 883 884 return true; 885 } 886 887 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, 888 int id, boolean forHun, int userId) { 889 return new NotificationClicker(intent, pkg, tag, id, forHun, userId); 890 } 891 892 protected class NotificationClicker implements View.OnClickListener { 893 private PendingIntent mIntent; 894 private String mPkg; 895 private String mTag; 896 private int mId; 897 private boolean mIsHeadsUp; 898 private int mUserId; 899 900 public NotificationClicker(PendingIntent intent, String pkg, String tag, int id, 901 boolean forHun, int userId) { 902 mIntent = intent; 903 mPkg = pkg; 904 mTag = tag; 905 mId = id; 906 mIsHeadsUp = forHun; 907 mUserId = userId; 908 } 909 910 public void onClick(View v) { 911 try { 912 // The intent we are sending is for the application, which 913 // won't have permission to immediately start an activity after 914 // the user switches to home. We know it is safe to do at this 915 // point, so make sure new activity switches are now allowed. 916 ActivityManagerNative.getDefault().resumeAppSwitches(); 917 // Also, notifications can be launched from the lock screen, 918 // so dismiss the lock screen when the activity starts. 919 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 920 } catch (RemoteException e) { 921 } 922 923 if (mIntent != null) { 924 int[] pos = new int[2]; 925 v.getLocationOnScreen(pos); 926 Intent overlay = new Intent(); 927 overlay.setSourceBounds( 928 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 929 try { 930 mIntent.send(mContext, 0, overlay); 931 } catch (PendingIntent.CanceledException e) { 932 // the stack trace isn't very helpful here. Just log the exception message. 933 Log.w(TAG, "Sending contentIntent failed: " + e); 934 } 935 936 KeyguardTouchDelegate.getInstance(mContext).dismiss(); 937 } 938 939 try { 940 if (mIsHeadsUp) { 941 mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); 942 } 943 mBarService.onNotificationClick(mPkg, mTag, mId, mUserId); 944 } catch (RemoteException ex) { 945 // system process is dead if we're here. 946 } 947 948 // close the shade if it was open 949 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 950 visibilityChanged(false); 951 } 952 } 953 954 /** 955 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 956 * This was added last-minute and is inconsistent with the way the rest of the notifications 957 * are handled, because the notification isn't really cancelled. The lights are just 958 * turned off. If any other notifications happen, the lights will turn back on. Steve says 959 * this is what he wants. (see bug 1131461) 960 */ 961 protected void visibilityChanged(boolean visible) { 962 if (mPanelSlightlyVisible != visible) { 963 mPanelSlightlyVisible = visible; 964 try { 965 if (visible) { 966 mBarService.onPanelRevealed(); 967 } else { 968 mBarService.onPanelHidden(); 969 } 970 } catch (RemoteException ex) { 971 // Won't fail unless the world has ended. 972 } 973 } 974 } 975 976 /** 977 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 978 * about the failure. 979 * 980 * WARNING: this will call back into us. Don't hold any locks. 981 */ 982 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 983 removeNotification(key); 984 try { 985 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), 986 n.getInitialPid(), message, n.getUserId()); 987 } catch (RemoteException ex) { 988 // The end is nigh. 989 } 990 } 991 992 protected StatusBarNotification removeNotificationViews(IBinder key) { 993 NotificationData.Entry entry = mNotificationData.remove(key); 994 if (entry == null) { 995 Log.w(TAG, "removeNotification for unknown key: " + key); 996 return null; 997 } 998 // Remove the expanded view. 999 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 1000 if (rowParent != null) rowParent.removeView(entry.row); 1001 updateRowStates(); 1002 updateNotificationIcons(); 1003 1004 return entry.notification; 1005 } 1006 1007 protected NotificationData.Entry createNotificationViews(IBinder key, 1008 StatusBarNotification notification) { 1009 if (DEBUG) { 1010 Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification); 1011 } 1012 // Construct the icon. 1013 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1014 notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()), 1015 notification.getNotification()); 1016 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1017 1018 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1019 notification.getUser(), 1020 notification.getNotification().icon, 1021 notification.getNotification().iconLevel, 1022 notification.getNotification().number, 1023 notification.getNotification().tickerText); 1024 if (!iconView.set(ic)) { 1025 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 1026 return null; 1027 } 1028 // Construct the expanded view. 1029 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 1030 if (!inflateViews(entry, mStackScroller)) { 1031 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 1032 + notification); 1033 return null; 1034 } 1035 return entry; 1036 } 1037 1038 protected void addNotificationViews(NotificationData.Entry entry) { 1039 // Add the expanded view and icon. 1040 int pos = mNotificationData.add(entry); 1041 if (DEBUG) { 1042 Log.d(TAG, "addNotificationViews: added at " + pos); 1043 } 1044 updateRowStates(); 1045 updateNotificationIcons(); 1046 } 1047 1048 private void addNotificationViews(IBinder key, StatusBarNotification notification) { 1049 addNotificationViews(createNotificationViews(key, notification)); 1050 } 1051 1052 /** 1053 * @return The number of notifications we show on Keyguard. 1054 */ 1055 protected abstract int getMaxKeyguardNotifications(); 1056 1057 /** 1058 * Updates expanded, dimmed and locked states of notification rows. 1059 */ 1060 protected void updateRowStates() { 1061 int maxKeyguardNotifications = getMaxKeyguardNotifications(); 1062 mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); 1063 int n = mNotificationData.size(); 1064 int visibleNotifications = 0; 1065 boolean onKeyguard = mState == StatusBarState.KEYGUARD; 1066 for (int i = n-1; i >= 0; i--) { 1067 NotificationData.Entry entry = mNotificationData.get(i); 1068 if (onKeyguard) { 1069 entry.row.setExpansionDisabled(true); 1070 } else { 1071 entry.row.setExpansionDisabled(false); 1072 if (!entry.row.isUserLocked()) { 1073 boolean top = (i == n-1); 1074 entry.row.setSystemExpanded(top); 1075 } 1076 } 1077 entry.row.setDimmed(onKeyguard, false /* fade */); 1078 boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); 1079 if (onKeyguard && (visibleNotifications >= maxKeyguardNotifications 1080 || !showOnKeyguard)) { 1081 entry.row.setVisibility(View.GONE); 1082 if (showOnKeyguard) { 1083 mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); 1084 } 1085 } else { 1086 if (entry.row.getVisibility() == View.GONE) { 1087 // notify the scroller of a child addition 1088 mStackScroller.generateAddAnimation(entry.row); 1089 } 1090 entry.row.setVisibility(View.VISIBLE); 1091 visibleNotifications++; 1092 } 1093 } 1094 1095 if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { 1096 mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE); 1097 mKeyguardIconOverflowContainer.setDimmed(true /* dimmed */, false /* fade */); 1098 } else { 1099 mKeyguardIconOverflowContainer.setVisibility(View.GONE); 1100 } 1101 } 1102 1103 @Override 1104 public void onActivated(View view) { 1105 int n = mNotificationData.size(); 1106 for (int i = 0; i < n; i++) { 1107 NotificationData.Entry entry = mNotificationData.get(i); 1108 if (entry.row.getVisibility() != View.GONE) { 1109 if (view == entry.row) { 1110 entry.row.getActivator().activate(); 1111 } else { 1112 entry.row.getActivator().activateInverse(); 1113 } 1114 } 1115 } 1116 if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) { 1117 if (view == mKeyguardIconOverflowContainer) { 1118 mKeyguardIconOverflowContainer.getActivator().activate(); 1119 } else { 1120 mKeyguardIconOverflowContainer.getActivator().activateInverse(); 1121 } 1122 } 1123 } 1124 1125 @Override 1126 public void onReset(View view) { 1127 int n = mNotificationData.size(); 1128 for (int i = 0; i < n; i++) { 1129 NotificationData.Entry entry = mNotificationData.get(i); 1130 if (entry.row.getVisibility() != View.GONE) { 1131 entry.row.getActivator().reset(); 1132 } 1133 } 1134 if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) { 1135 mKeyguardIconOverflowContainer.getActivator().reset(); 1136 } 1137 } 1138 1139 private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { 1140 return sbn.getNotification().priority >= Notification.PRIORITY_LOW; 1141 } 1142 1143 protected void setZenMode(int mode) { 1144 if (!isDeviceProvisioned()) return; 1145 mZenMode = mode; 1146 updateNotificationIcons(); 1147 } 1148 1149 protected abstract void haltTicker(); 1150 protected abstract void setAreThereNotifications(); 1151 protected abstract void updateNotificationIcons(); 1152 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 1153 protected abstract void updateExpandedViewPos(int expandedPosition); 1154 protected abstract int getExpandedViewMaxHeight(); 1155 protected abstract boolean shouldDisableNavbarGestures(); 1156 1157 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 1158 return parent != null && parent.indexOfChild(entry.row) == 0; 1159 } 1160 1161 public void updateNotification(IBinder key, StatusBarNotification notification) { 1162 if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 1163 1164 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 1165 if (oldEntry == null) { 1166 Log.w(TAG, "updateNotification for unknown key: " + key); 1167 return; 1168 } 1169 1170 final StatusBarNotification oldNotification = oldEntry.notification; 1171 1172 // XXX: modify when we do something more intelligent with the two content views 1173 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 1174 final RemoteViews contentView = notification.getNotification().contentView; 1175 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 1176 final RemoteViews bigContentView = notification.getNotification().bigContentView; 1177 final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView; 1178 final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView; 1179 final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; 1180 final RemoteViews oldPublicContentView = oldPublicNotification != null 1181 ? oldPublicNotification.contentView : null; 1182 final Notification publicNotification = notification.getNotification().publicVersion; 1183 final RemoteViews publicContentView = publicNotification != null 1184 ? publicNotification.contentView : null; 1185 1186 if (DEBUG) { 1187 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 1188 + " ongoing=" + oldNotification.isOngoing() 1189 + " expanded=" + oldEntry.expanded 1190 + " contentView=" + oldContentView 1191 + " bigContentView=" + oldBigContentView 1192 + " publicView=" + oldPublicContentView 1193 + " rowParent=" + oldEntry.row.getParent()); 1194 Log.d(TAG, "new notification: when=" + notification.getNotification().when 1195 + " ongoing=" + oldNotification.isOngoing() 1196 + " contentView=" + contentView 1197 + " bigContentView=" + bigContentView 1198 + " publicView=" + publicContentView); 1199 } 1200 1201 // Can we just reapply the RemoteViews in place? If when didn't change, the order 1202 // didn't change. 1203 1204 // 1U is never null 1205 boolean contentsUnchanged = oldEntry.expanded != null 1206 && contentView.getPackage() != null 1207 && oldContentView.getPackage() != null 1208 && oldContentView.getPackage().equals(contentView.getPackage()) 1209 && oldContentView.getLayoutId() == contentView.getLayoutId(); 1210 // large view may be null 1211 boolean bigContentsUnchanged = 1212 (oldEntry.getBigContentView() == null && bigContentView == null) 1213 || ((oldEntry.getBigContentView() != null && bigContentView != null) 1214 && bigContentView.getPackage() != null 1215 && oldBigContentView.getPackage() != null 1216 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 1217 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 1218 boolean headsUpContentsUnchanged = 1219 (oldHeadsUpContentView == null && headsUpContentView == null) 1220 || ((oldHeadsUpContentView != null && headsUpContentView != null) 1221 && headsUpContentView.getPackage() != null 1222 && oldHeadsUpContentView.getPackage() != null 1223 && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) 1224 && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); 1225 boolean publicUnchanged = 1226 (oldPublicContentView == null && publicContentView == null) 1227 || ((oldPublicContentView != null && publicContentView != null) 1228 && publicContentView.getPackage() != null 1229 && oldPublicContentView.getPackage() != null 1230 && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) 1231 && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); 1232 1233 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 1234 boolean orderUnchanged = 1235 notification.getNotification().when == oldNotification.getNotification().when 1236 && notification.getScore() == oldNotification.getScore(); 1237 // score now encompasses/supersedes isOngoing() 1238 1239 boolean updateTicker = notification.getNotification().tickerText != null 1240 && !TextUtils.equals(notification.getNotification().tickerText, 1241 oldEntry.notification.getNotification().tickerText); 1242 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 1243 if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged && publicUnchanged 1244 && (orderUnchanged || isTopAnyway)) { 1245 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); 1246 oldEntry.notification = notification; 1247 try { 1248 updateNotificationViews(oldEntry, notification); 1249 1250 if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null 1251 && oldNotification == mInterruptingNotificationEntry.notification) { 1252 if (!shouldInterrupt(notification)) { 1253 if (DEBUG) Log.d(TAG, "no longer interrupts!"); 1254 mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); 1255 } else { 1256 if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification); 1257 mInterruptingNotificationEntry.notification = notification; 1258 updateHeadsUpViews(mInterruptingNotificationEntry, notification); 1259 } 1260 } 1261 1262 // Update the icon. 1263 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1264 notification.getUser(), 1265 notification.getNotification().icon, notification.getNotification().iconLevel, 1266 notification.getNotification().number, 1267 notification.getNotification().tickerText); 1268 if (!oldEntry.icon.set(ic)) { 1269 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 1270 return; 1271 } 1272 updateRowStates(); 1273 } 1274 catch (RuntimeException e) { 1275 // It failed to add cleanly. Log, and remove the view from the panel. 1276 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1277 removeNotificationViews(key); 1278 addNotificationViews(key, notification); 1279 } 1280 } else { 1281 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); 1282 if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 1283 if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); 1284 if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); 1285 removeNotificationViews(key); 1286 addNotificationViews(key, notification); // will also replace the heads up 1287 final NotificationData.Entry newEntry = mNotificationData.findByKey(key); 1288 final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion(); 1289 if (userChangedExpansion) { 1290 boolean userExpanded = oldEntry.row.isUserExpanded(); 1291 newEntry.row.setUserExpanded(userExpanded); 1292 newEntry.row.notifyHeightChanged(); 1293 } 1294 } 1295 1296 // Update the veto button accordingly (and as a result, whether this row is 1297 // swipe-dismissable) 1298 updateNotificationVetoButton(oldEntry.row, notification); 1299 1300 // Is this for you? 1301 boolean isForCurrentUser = notificationIsForCurrentProfiles(notification); 1302 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1303 1304 // Restart the ticker if it's still running 1305 if (updateTicker && isForCurrentUser) { 1306 haltTicker(); 1307 tick(key, notification, false); 1308 } 1309 1310 // Recalculate the position of the sliding windows and the titles. 1311 setAreThereNotifications(); 1312 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1313 } 1314 1315 private void updateNotificationViews(NotificationData.Entry entry, 1316 StatusBarNotification notification) { 1317 updateNotificationViews(entry, notification, false); 1318 } 1319 1320 private void updateHeadsUpViews(NotificationData.Entry entry, 1321 StatusBarNotification notification) { 1322 updateNotificationViews(entry, notification, true); 1323 } 1324 1325 private void updateNotificationViews(NotificationData.Entry entry, 1326 StatusBarNotification notification, boolean isHeadsUp) { 1327 final RemoteViews contentView = notification.getNotification().contentView; 1328 final RemoteViews bigContentView = isHeadsUp 1329 ? notification.getNotification().headsUpContentView 1330 : notification.getNotification().bigContentView; 1331 final Notification publicVersion = notification.getNotification().publicVersion; 1332 final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView 1333 : null; 1334 1335 // Reapply the RemoteViews 1336 contentView.reapply(mContext, entry.expanded, mOnClickHandler); 1337 if (bigContentView != null && entry.getBigContentView() != null) { 1338 bigContentView.reapply(mContext, entry.getBigContentView(), 1339 mOnClickHandler); 1340 } 1341 if (publicContentView != null && entry.getPublicContentView() != null) { 1342 publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); 1343 } 1344 // update the contentIntent 1345 final PendingIntent contentIntent = notification.getNotification().contentIntent; 1346 if (contentIntent != null) { 1347 final View.OnClickListener listener = makeClicker(contentIntent, 1348 notification.getPackageName(), notification.getTag(), notification.getId(), 1349 isHeadsUp, notification.getUserId()); 1350 entry.row.setOnClickListener(listener); 1351 } else { 1352 entry.row.setOnClickListener(null); 1353 } 1354 entry.row.notifyContentUpdated(); 1355 } 1356 1357 protected void notifyHeadsUpScreenOn(boolean screenOn) { 1358 if (!screenOn && mInterruptingNotificationEntry != null) { 1359 mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); 1360 } 1361 } 1362 1363 protected boolean shouldInterrupt(StatusBarNotification sbn) { 1364 Notification notification = sbn.getNotification(); 1365 // some predicates to make the boolean logic legible 1366 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 1367 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 1368 || notification.sound != null 1369 || notification.vibrate != null; 1370 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 1371 boolean isFullscreen = notification.fullScreenIntent != null; 1372 boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText); 1373 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 1374 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 1375 1376 final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext); 1377 boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker))) 1378 && isAllowed 1379 && mPowerManager.isScreenOn() 1380 && !keyguard.isShowingAndNotOccluded() 1381 && !keyguard.isInputRestricted(); 1382 try { 1383 interrupt = interrupt && !mDreamManager.isDreaming(); 1384 } catch (RemoteException e) { 1385 Log.d(TAG, "failed to query dream manager", e); 1386 } 1387 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 1388 return interrupt; 1389 } 1390 1391 // Q: What kinds of notifications should show during setup? 1392 // A: Almost none! Only things coming from the system (package is "android") that also 1393 // have special "kind" tags marking them as relevant for setup (see below). 1394 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 1395 return "android".equals(sbn.getPackageName()) 1396 && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP); 1397 } 1398 1399 public boolean inKeyguardRestrictedInputMode() { 1400 return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted(); 1401 } 1402 1403 public void setInteracting(int barWindow, boolean interacting) { 1404 // hook for subclasses 1405 } 1406 1407 public void setBouncerShowing(boolean bouncerShowing) { 1408 mBouncerShowing = bouncerShowing; 1409 } 1410 1411 /** 1412 * @return Whether the security bouncer from Keyguard is showing. 1413 */ 1414 public boolean isBouncerShowing() { 1415 return mBouncerShowing; 1416 } 1417 1418 public void destroy() { 1419 if (mSearchPanelView != null) { 1420 mWindowManager.removeViewImmediate(mSearchPanelView); 1421 } 1422 mContext.unregisterReceiver(mBroadcastReceiver); 1423 } 1424} 1425