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