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