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