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 17 package com.android.systemui.statusbar; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.TimeInterpolator; 22 import android.app.ActivityManager; 23 import android.app.ActivityManagerNative; 24 import android.app.Notification; 25 import android.app.NotificationManager; 26 import android.app.PendingIntent; 27 import android.app.TaskStackBuilder; 28 import android.app.admin.DevicePolicyManager; 29 import android.content.BroadcastReceiver; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageManager; 36 import android.content.pm.PackageManager.NameNotFoundException; 37 import android.content.pm.ResolveInfo; 38 import android.content.pm.UserInfo; 39 import android.content.res.Configuration; 40 import android.content.res.Resources; 41 import android.database.ContentObserver; 42 import android.graphics.PorterDuff; 43 import android.graphics.drawable.Drawable; 44 import android.os.AsyncTask; 45 import android.os.Build; 46 import android.os.Handler; 47 import android.os.IBinder; 48 import android.os.Message; 49 import android.os.PowerManager; 50 import android.os.RemoteException; 51 import android.os.ServiceManager; 52 import android.os.UserHandle; 53 import android.os.UserManager; 54 import android.provider.Settings; 55 import android.service.dreams.DreamService; 56 import android.service.dreams.IDreamManager; 57 import android.service.notification.NotificationListenerService; 58 import android.service.notification.NotificationListenerService.RankingMap; 59 import android.service.notification.StatusBarNotification; 60 import android.text.TextUtils; 61 import android.util.Log; 62 import android.util.SparseArray; 63 import android.util.SparseBooleanArray; 64 import android.view.Display; 65 import android.view.IWindowManager; 66 import android.view.LayoutInflater; 67 import android.view.MotionEvent; 68 import android.view.View; 69 import android.view.ViewAnimationUtils; 70 import android.view.ViewGroup; 71 import android.view.ViewGroup.LayoutParams; 72 import android.view.ViewStub; 73 import android.view.WindowManager; 74 import android.view.WindowManagerGlobal; 75 import android.view.accessibility.AccessibilityManager; 76 import android.view.animation.AnimationUtils; 77 import android.widget.DateTimeView; 78 import android.widget.ImageView; 79 import android.widget.LinearLayout; 80 import android.widget.RemoteViews; 81 import android.widget.TextView; 82 83 import com.android.internal.statusbar.IStatusBarService; 84 import com.android.internal.statusbar.StatusBarIcon; 85 import com.android.internal.statusbar.StatusBarIconList; 86 import com.android.internal.util.NotificationColorUtil; 87 import com.android.internal.widget.LockPatternUtils; 88 import com.android.systemui.R; 89 import com.android.systemui.RecentsComponent; 90 import com.android.systemui.SearchPanelView; 91 import com.android.systemui.SwipeHelper; 92 import com.android.systemui.SystemUI; 93 import com.android.systemui.statusbar.NotificationData.Entry; 94 import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; 95 import com.android.systemui.statusbar.phone.NavigationBarView; 96 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 97 import com.android.systemui.statusbar.policy.HeadsUpNotificationView; 98 import com.android.systemui.statusbar.policy.PreviewInflater; 99 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 100 101 import java.util.ArrayList; 102 import java.util.List; 103 import java.util.Locale; 104 105 import static com.android.keyguard.KeyguardHostView.OnDismissAction; 106 107 public abstract class BaseStatusBar extends SystemUI implements 108 CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener, 109 RecentsComponent.Callbacks, ExpandableNotificationRow.ExpansionLogger, 110 NotificationData.Environment { 111 public static final String TAG = "StatusBar"; 112 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 113 public static final boolean MULTIUSER_DEBUG = false; 114 115 protected static final int MSG_SHOW_RECENT_APPS = 1019; 116 protected static final int MSG_HIDE_RECENT_APPS = 1020; 117 protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; 118 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 119 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 120 protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024; 121 protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025; 122 protected static final int MSG_CLOSE_SEARCH_PANEL = 1027; 123 protected static final int MSG_SHOW_HEADS_UP = 1028; 124 protected static final int MSG_HIDE_HEADS_UP = 1029; 125 protected static final int MSG_ESCALATE_HEADS_UP = 1030; 126 protected static final int MSG_DECAY_HEADS_UP = 1031; 127 128 protected static final boolean ENABLE_HEADS_UP = true; 129 // scores above this threshold should be displayed in heads up mode. 130 protected static final int INTERRUPTION_THRESHOLD = 10; 131 protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; 132 133 // Should match the value in PhoneWindowManager 134 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 135 136 public static final int EXPANDED_LEAVE_ALONE = -10000; 137 public static final int EXPANDED_FULL_OPEN = -10001; 138 139 private static final int HIDDEN_NOTIFICATION_ID = 10000; 140 private static final String BANNER_ACTION_CANCEL = 141 "com.android.systemui.statusbar.banner_action_cancel"; 142 private static final String BANNER_ACTION_SETUP = 143 "com.android.systemui.statusbar.banner_action_setup"; 144 145 protected CommandQueue mCommandQueue; 146 protected IStatusBarService mBarService; 147 protected H mHandler = createHandler(); 148 149 // all notifications 150 protected NotificationData mNotificationData; 151 protected NotificationStackScrollLayout mStackScroller; 152 153 // for heads up notifications 154 protected HeadsUpNotificationView mHeadsUpNotificationView; 155 protected int mHeadsUpNotificationDecay; 156 157 // used to notify status bar for suppressing notification LED 158 protected boolean mPanelSlightlyVisible; 159 160 // Search panel 161 protected SearchPanelView mSearchPanelView; 162 163 protected int mCurrentUserId = 0; 164 final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); 165 166 protected int mLayoutDirection = -1; // invalid 167 protected AccessibilityManager mAccessibilityManager; 168 169 // on-screen navigation buttons 170 protected NavigationBarView mNavigationBarView = null; 171 private Locale mLocale; 172 private float mFontScale; 173 174 protected boolean mUseHeadsUp = false; 175 protected boolean mHeadsUpTicker = false; 176 protected boolean mDisableNotificationAlerts = false; 177 178 protected DevicePolicyManager mDevicePolicyManager; 179 protected IDreamManager mDreamManager; 180 PowerManager mPowerManager; 181 protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 182 protected int mRowMinHeight; 183 protected int mRowMaxHeight; 184 185 // public mode, private notifications, etc 186 private boolean mLockscreenPublicMode = false; 187 private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); 188 private NotificationColorUtil mNotificationColorUtil; 189 190 private UserManager mUserManager; 191 192 // UI-specific methods 193 194 /** 195 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 196 * and add them to the window manager. 197 */ 198 protected abstract void createAndAddWindows(); 199 200 protected WindowManager mWindowManager; 201 protected IWindowManager mWindowManagerService; 202 203 protected abstract void refreshLayout(int layoutDirection); 204 205 protected Display mDisplay; 206 207 private boolean mDeviceProvisioned = false; 208 209 private RecentsComponent mRecents; 210 211 protected int mZenMode; 212 213 // which notification is currently being longpress-examined by the user 214 private NotificationGuts mNotificationGutsExposed; 215 216 private TimeInterpolator mLinearOutSlowIn, mFastOutLinearIn; 217 218 /** 219 * The {@link StatusBarState} of the status bar. 220 */ 221 protected int mState; 222 protected boolean mBouncerShowing; 223 protected boolean mShowLockscreenNotifications; 224 225 protected NotificationOverflowContainer mKeyguardIconOverflowContainer; 226 protected DismissView mDismissView; 227 protected EmptyShadeView mEmptyShadeView; 228 229 @Override // NotificationData.Environment 230 public boolean isDeviceProvisioned() { 231 return mDeviceProvisioned; 232 } 233 234 protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 235 @Override 236 public void onChange(boolean selfChange) { 237 final boolean provisioned = 0 != Settings.Global.getInt( 238 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); 239 if (provisioned != mDeviceProvisioned) { 240 mDeviceProvisioned = provisioned; 241 updateNotifications(); 242 } 243 final int mode = Settings.Global.getInt(mContext.getContentResolver(), 244 Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); 245 setZenMode(mode); 246 247 updateLockscreenNotificationSetting(); 248 } 249 }; 250 251 private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) { 252 @Override 253 public void onChange(boolean selfChange) { 254 // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 255 // so we just dump our cache ... 256 mUsersAllowingPrivateNotifications.clear(); 257 // ... and refresh all the notifications 258 updateNotifications(); 259 } 260 }; 261 262 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { 263 @Override 264 public boolean onClickHandler( 265 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { 266 if (DEBUG) { 267 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); 268 } 269 // The intent we are sending is for the application, which 270 // won't have permission to immediately start an activity after 271 // the user switches to home. We know it is safe to do at this 272 // point, so make sure new activity switches are now allowed. 273 try { 274 ActivityManagerNative.getDefault().resumeAppSwitches(); 275 } catch (RemoteException e) { 276 } 277 final boolean isActivity = pendingIntent.isActivity(); 278 if (isActivity) { 279 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 280 final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( 281 mContext, pendingIntent.getIntent(), mCurrentUserId); 282 dismissKeyguardThenExecute(new OnDismissAction() { 283 @Override 284 public boolean onDismiss() { 285 if (keyguardShowing && !afterKeyguardGone) { 286 try { 287 ActivityManagerNative.getDefault() 288 .keyguardWaitingForActivityDrawn(); 289 } catch (RemoteException e) { 290 } 291 } 292 293 boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent); 294 overrideActivityPendingAppTransition(keyguardShowing && !afterKeyguardGone); 295 296 // close the shade if it was open 297 if (handled) { 298 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 299 visibilityChanged(false); 300 } 301 // Wait for activity start. 302 return handled; 303 } 304 }, afterKeyguardGone); 305 return true; 306 } else { 307 return super.onClickHandler(view, pendingIntent, fillInIntent); 308 } 309 } 310 311 private boolean superOnClickHandler(View view, PendingIntent pendingIntent, 312 Intent fillInIntent) { 313 return super.onClickHandler(view, pendingIntent, fillInIntent); 314 } 315 }; 316 317 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 318 @Override 319 public void onReceive(Context context, Intent intent) { 320 String action = intent.getAction(); 321 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 322 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 323 updateCurrentProfilesCache(); 324 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); 325 326 updateLockscreenNotificationSetting(); 327 328 userSwitched(mCurrentUserId); 329 } else if (Intent.ACTION_USER_ADDED.equals(action)) { 330 updateCurrentProfilesCache(); 331 } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals( 332 action)) { 333 mUsersAllowingPrivateNotifications.clear(); 334 updateLockscreenNotificationSetting(); 335 updateNotifications(); 336 } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { 337 NotificationManager noMan = (NotificationManager) 338 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 339 noMan.cancel(HIDDEN_NOTIFICATION_ID); 340 341 Settings.Secure.putInt(mContext.getContentResolver(), 342 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); 343 if (BANNER_ACTION_SETUP.equals(action)) { 344 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 345 mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) 346 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 347 348 ); 349 } 350 } 351 } 352 }; 353 354 private final NotificationListenerService mNotificationListener = 355 new NotificationListenerService() { 356 @Override 357 public void onListenerConnected() { 358 if (DEBUG) Log.d(TAG, "onListenerConnected"); 359 final StatusBarNotification[] notifications = getActiveNotifications(); 360 final RankingMap currentRanking = getCurrentRanking(); 361 mHandler.post(new Runnable() { 362 @Override 363 public void run() { 364 for (StatusBarNotification sbn : notifications) { 365 addNotification(sbn, currentRanking); 366 } 367 } 368 }); 369 } 370 371 @Override 372 public void onNotificationPosted(final StatusBarNotification sbn, 373 final RankingMap rankingMap) { 374 if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); 375 mHandler.post(new Runnable() { 376 @Override 377 public void run() { 378 Notification n = sbn.getNotification(); 379 boolean isUpdate = mNotificationData.get(sbn.getKey()) != null 380 || isHeadsUp(sbn.getKey()); 381 382 // Ignore children of notifications that have a summary, since we're not 383 // going to show them anyway. This is true also when the summary is canceled, 384 // because children are automatically canceled by NoMan in that case. 385 if (n.isGroupChild() && 386 mNotificationData.isGroupWithSummary(sbn.getGroupKey())) { 387 if (DEBUG) { 388 Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); 389 } 390 391 // Remove existing notification to avoid stale data. 392 if (isUpdate) { 393 removeNotification(sbn.getKey(), rankingMap); 394 } else { 395 mNotificationData.updateRanking(rankingMap); 396 } 397 return; 398 } 399 if (isUpdate) { 400 updateNotification(sbn, rankingMap); 401 } else { 402 addNotification(sbn, rankingMap); 403 } 404 } 405 }); 406 } 407 408 @Override 409 public void onNotificationRemoved(final StatusBarNotification sbn, 410 final RankingMap rankingMap) { 411 if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); 412 mHandler.post(new Runnable() { 413 @Override 414 public void run() { 415 removeNotification(sbn.getKey(), rankingMap); 416 } 417 }); 418 } 419 420 @Override 421 public void onNotificationRankingUpdate(final RankingMap rankingMap) { 422 if (DEBUG) Log.d(TAG, "onRankingUpdate"); 423 mHandler.post(new Runnable() { 424 @Override 425 public void run() { 426 updateNotificationRanking(rankingMap); 427 } 428 }); 429 } 430 431 }; 432 433 private void updateCurrentProfilesCache() { 434 synchronized (mCurrentProfiles) { 435 mCurrentProfiles.clear(); 436 if (mUserManager != null) { 437 for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { 438 mCurrentProfiles.put(user.id, user); 439 } 440 } 441 } 442 } 443 444 public void start() { 445 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 446 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 447 mDisplay = mWindowManager.getDefaultDisplay(); 448 mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService( 449 Context.DEVICE_POLICY_SERVICE); 450 451 mNotificationColorUtil = NotificationColorUtil.getInstance(mContext); 452 453 mNotificationData = new NotificationData(this); 454 455 mAccessibilityManager = (AccessibilityManager) 456 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); 457 458 mDreamManager = IDreamManager.Stub.asInterface( 459 ServiceManager.checkService(DreamService.DREAM_SERVICE)); 460 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 461 462 mSettingsObserver.onChange(false); // set up 463 mContext.getContentResolver().registerContentObserver( 464 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, 465 mSettingsObserver); 466 mContext.getContentResolver().registerContentObserver( 467 Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, 468 mSettingsObserver); 469 mContext.getContentResolver().registerContentObserver( 470 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, 471 mSettingsObserver, 472 UserHandle.USER_ALL); 473 474 mContext.getContentResolver().registerContentObserver( 475 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), 476 true, 477 mLockscreenSettingsObserver, 478 UserHandle.USER_ALL); 479 480 mBarService = IStatusBarService.Stub.asInterface( 481 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 482 483 mRecents = getComponent(RecentsComponent.class); 484 mRecents.setCallback(this); 485 486 final Configuration currentConfig = mContext.getResources().getConfiguration(); 487 mLocale = currentConfig.locale; 488 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 489 mFontScale = currentConfig.fontScale; 490 491 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 492 493 mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext, 494 android.R.interpolator.linear_out_slow_in); 495 mFastOutLinearIn = AnimationUtils.loadInterpolator(mContext, 496 android.R.interpolator.fast_out_linear_in); 497 498 // Connect in to the status bar manager service 499 StatusBarIconList iconList = new StatusBarIconList(); 500 mCommandQueue = new CommandQueue(this, iconList); 501 502 int[] switches = new int[8]; 503 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 504 try { 505 mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders); 506 } catch (RemoteException ex) { 507 // If the system process isn't there we're doomed anyway. 508 } 509 510 createAndAddWindows(); 511 512 disable(switches[0], false /* animate */); 513 setSystemUiVisibility(switches[1], 0xffffffff); 514 topAppWindowChanged(switches[2] != 0); 515 // StatusBarManagerService has a back up of IME token and it's restored here. 516 setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0); 517 518 // Set up the initial icon state 519 int N = iconList.size(); 520 int viewIndex = 0; 521 for (int i=0; i<N; i++) { 522 StatusBarIcon icon = iconList.getIcon(i); 523 if (icon != null) { 524 addIcon(iconList.getSlot(i), i, viewIndex, icon); 525 viewIndex++; 526 } 527 } 528 529 // Set up the initial notification state. 530 try { 531 mNotificationListener.registerAsSystemService(mContext, 532 new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), 533 UserHandle.USER_ALL); 534 } catch (RemoteException e) { 535 Log.e(TAG, "Unable to register notification listener", e); 536 } 537 538 539 if (DEBUG) { 540 Log.d(TAG, String.format( 541 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 542 iconList.size(), 543 switches[0], 544 switches[1], 545 switches[2], 546 switches[3] 547 )); 548 } 549 550 mCurrentUserId = ActivityManager.getCurrentUser(); 551 552 IntentFilter filter = new IntentFilter(); 553 filter.addAction(Intent.ACTION_USER_SWITCHED); 554 filter.addAction(Intent.ACTION_USER_ADDED); 555 filter.addAction(BANNER_ACTION_CANCEL); 556 filter.addAction(BANNER_ACTION_SETUP); 557 filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); 558 mContext.registerReceiver(mBroadcastReceiver, filter); 559 560 updateCurrentProfilesCache(); 561 } 562 563 protected void notifyUserAboutHiddenNotifications() { 564 if (0 != Settings.Secure.getInt(mContext.getContentResolver(), 565 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) { 566 Log.d(TAG, "user hasn't seen notification about hidden notifications"); 567 final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); 568 if (!lockPatternUtils.isSecure()) { 569 Log.d(TAG, "insecure lockscreen, skipping notification"); 570 Settings.Secure.putInt(mContext.getContentResolver(), 571 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); 572 return; 573 } 574 Log.d(TAG, "disabling lockecreen notifications and alerting the user"); 575 // disable lockscreen notifications until user acts on the banner. 576 Settings.Secure.putInt(mContext.getContentResolver(), 577 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); 578 Settings.Secure.putInt(mContext.getContentResolver(), 579 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); 580 581 final String packageName = mContext.getPackageName(); 582 PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0, 583 new Intent(BANNER_ACTION_CANCEL).setPackage(packageName), 584 PendingIntent.FLAG_CANCEL_CURRENT); 585 PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0, 586 new Intent(BANNER_ACTION_SETUP).setPackage(packageName), 587 PendingIntent.FLAG_CANCEL_CURRENT); 588 589 final Resources res = mContext.getResources(); 590 final int colorRes = com.android.internal.R.color.system_notification_accent_color; 591 Notification.Builder note = new Notification.Builder(mContext) 592 .setSmallIcon(R.drawable.ic_android) 593 .setContentTitle(mContext.getString(R.string.hidden_notifications_title)) 594 .setContentText(mContext.getString(R.string.hidden_notifications_text)) 595 .setPriority(Notification.PRIORITY_HIGH) 596 .setOngoing(true) 597 .setColor(res.getColor(colorRes)) 598 .setContentIntent(setupIntent) 599 .addAction(R.drawable.ic_close, 600 mContext.getString(R.string.hidden_notifications_cancel), 601 cancelIntent) 602 .addAction(R.drawable.ic_settings, 603 mContext.getString(R.string.hidden_notifications_setup), 604 setupIntent); 605 606 NotificationManager noMan = 607 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 608 noMan.notify(HIDDEN_NOTIFICATION_ID, note.build()); 609 } 610 } 611 612 public void userSwitched(int newUserId) { 613 // should be overridden 614 } 615 616 public boolean isHeadsUp(String key) { 617 return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key); 618 } 619 620 @Override // NotificationData.Environment 621 public boolean isNotificationForCurrentProfiles(StatusBarNotification n) { 622 final int thisUserId = mCurrentUserId; 623 final int notificationUserId = n.getUserId(); 624 if (DEBUG && MULTIUSER_DEBUG) { 625 Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", 626 n, thisUserId, notificationUserId)); 627 } 628 synchronized (mCurrentProfiles) { 629 return notificationUserId == UserHandle.USER_ALL 630 || mCurrentProfiles.get(notificationUserId) != null; 631 } 632 } 633 634 @Override 635 public String getCurrentMediaNotificationKey() { 636 return null; 637 } 638 639 /** 640 * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. 641 * @param action A dismiss action that is called if it's safe to start the activity. 642 * @param afterKeyguardGone Whether the action should be executed after the Keyguard is gone. 643 */ 644 protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { 645 action.onDismiss(); 646 } 647 648 @Override 649 protected void onConfigurationChanged(Configuration newConfig) { 650 final Locale locale = mContext.getResources().getConfiguration().locale; 651 final int ld = TextUtils.getLayoutDirectionFromLocale(locale); 652 final float fontScale = newConfig.fontScale; 653 654 if (! locale.equals(mLocale) || ld != mLayoutDirection || fontScale != mFontScale) { 655 if (DEBUG) { 656 Log.v(TAG, String.format( 657 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, 658 locale, ld)); 659 } 660 mLocale = locale; 661 mLayoutDirection = ld; 662 refreshLayout(ld); 663 } 664 } 665 666 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 667 View vetoButton = row.findViewById(R.id.veto); 668 if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null 669 && mHeadsUpNotificationView.getEntry().row == row)) { 670 final String _pkg = n.getPackageName(); 671 final String _tag = n.getTag(); 672 final int _id = n.getId(); 673 final int _userId = n.getUserId(); 674 vetoButton.setOnClickListener(new View.OnClickListener() { 675 public void onClick(View v) { 676 // Accessibility feedback 677 v.announceForAccessibility( 678 mContext.getString(R.string.accessibility_notification_dismissed)); 679 try { 680 mBarService.onNotificationClear(_pkg, _tag, _id, _userId); 681 682 } catch (RemoteException ex) { 683 // system process is dead if we're here. 684 } 685 } 686 }); 687 vetoButton.setVisibility(View.VISIBLE); 688 } else { 689 vetoButton.setVisibility(View.GONE); 690 } 691 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 692 return vetoButton; 693 } 694 695 696 protected void applyColorsAndBackgrounds(StatusBarNotification sbn, 697 NotificationData.Entry entry) { 698 699 if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) { 700 // Using custom RemoteViews 701 if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD 702 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) { 703 entry.row.setShowingLegacyBackground(true); 704 entry.legacy = true; 705 } 706 } else { 707 // Using platform templates 708 final int color = sbn.getNotification().color; 709 if (isMediaNotification(entry)) { 710 entry.row.setTintColor(color == Notification.COLOR_DEFAULT 711 ? mContext.getResources().getColor( 712 R.color.notification_material_background_media_default_color) 713 : color); 714 } 715 } 716 717 if (entry.icon != null) { 718 if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP) { 719 entry.icon.setColorFilter(mContext.getResources().getColor(android.R.color.white)); 720 } else { 721 entry.icon.setColorFilter(null); 722 } 723 } 724 } 725 726 public boolean isMediaNotification(NotificationData.Entry entry) { 727 // TODO: confirm that there's a valid media key 728 return entry.expandedBig != null && 729 entry.expandedBig.findViewById(com.android.internal.R.id.media_actions) != null; 730 } 731 732 // The gear button in the guts that links to the app's own notification settings 733 private void startAppOwnNotificationSettingsActivity(Intent intent, 734 final int notificationId, final String notificationTag, final int appUid) { 735 intent.putExtra("notification_id", notificationId); 736 intent.putExtra("notification_tag", notificationTag); 737 startNotificationGutsIntent(intent, appUid); 738 } 739 740 // The (i) button in the guts that links to the system notification settings for that app 741 private void startAppNotificationSettingsActivity(String packageName, final int appUid) { 742 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 743 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); 744 intent.putExtra(Settings.EXTRA_APP_UID, appUid); 745 startNotificationGutsIntent(intent, appUid); 746 } 747 748 private void startNotificationGutsIntent(final Intent intent, final int appUid) { 749 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 750 dismissKeyguardThenExecute(new OnDismissAction() { 751 @Override 752 public boolean onDismiss() { 753 AsyncTask.execute(new Runnable() { 754 public void run() { 755 try { 756 if (keyguardShowing) { 757 ActivityManagerNative.getDefault() 758 .keyguardWaitingForActivityDrawn(); 759 } 760 TaskStackBuilder.create(mContext) 761 .addNextIntentWithParentStack(intent) 762 .startActivities(null, 763 new UserHandle(UserHandle.getUserId(appUid))); 764 overrideActivityPendingAppTransition(keyguardShowing); 765 } catch (RemoteException e) { 766 } 767 } 768 }); 769 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 770 return true; 771 } 772 }, false /* afterKeyguardGone */); 773 } 774 775 private void inflateGuts(ExpandableNotificationRow row) { 776 ViewStub stub = (ViewStub) row.findViewById(R.id.notification_guts_stub); 777 if (stub != null) { 778 stub.inflate(); 779 } 780 final StatusBarNotification sbn = row.getStatusBarNotification(); 781 PackageManager pmUser = getPackageManagerForUser( 782 sbn.getUser().getIdentifier()); 783 row.setTag(sbn.getPackageName()); 784 final View guts = row.findViewById(R.id.notification_guts); 785 final String pkg = sbn.getPackageName(); 786 String appname = pkg; 787 Drawable pkgicon = null; 788 int appUid = -1; 789 try { 790 final ApplicationInfo info = pmUser.getApplicationInfo(pkg, 791 PackageManager.GET_UNINSTALLED_PACKAGES 792 | PackageManager.GET_DISABLED_COMPONENTS); 793 if (info != null) { 794 appname = String.valueOf(pmUser.getApplicationLabel(info)); 795 pkgicon = pmUser.getApplicationIcon(info); 796 appUid = info.uid; 797 } 798 } catch (NameNotFoundException e) { 799 // app is gone, just show package name and generic icon 800 pkgicon = pmUser.getDefaultActivityIcon(); 801 } 802 ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon); 803 ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(sbn.getPostTime()); 804 ((TextView) row.findViewById(R.id.pkgname)).setText(appname); 805 final View settingsButton = guts.findViewById(R.id.notification_inspect_item); 806 final View appSettingsButton 807 = guts.findViewById(R.id.notification_inspect_app_provided_settings); 808 if (appUid >= 0) { 809 final int appUidF = appUid; 810 settingsButton.setOnClickListener(new View.OnClickListener() { 811 public void onClick(View v) { 812 startAppNotificationSettingsActivity(pkg, appUidF); 813 } 814 }); 815 816 final Intent appSettingsQueryIntent 817 = new Intent(Intent.ACTION_MAIN) 818 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) 819 .setPackage(pkg); 820 List<ResolveInfo> infos = pmUser.queryIntentActivities(appSettingsQueryIntent, 0); 821 if (infos.size() > 0) { 822 appSettingsButton.setVisibility(View.VISIBLE); 823 appSettingsButton.setContentDescription( 824 mContext.getResources().getString( 825 R.string.status_bar_notification_app_settings_title, 826 appname 827 )); 828 final Intent appSettingsLaunchIntent = new Intent(appSettingsQueryIntent) 829 .setClassName(pkg, infos.get(0).activityInfo.name); 830 appSettingsButton.setOnClickListener(new View.OnClickListener() { 831 public void onClick(View v) { 832 startAppOwnNotificationSettingsActivity(appSettingsLaunchIntent, 833 sbn.getId(), 834 sbn.getTag(), 835 appUidF); 836 } 837 }); 838 } else { 839 appSettingsButton.setVisibility(View.GONE); 840 } 841 } else { 842 settingsButton.setVisibility(View.GONE); 843 appSettingsButton.setVisibility(View.GONE); 844 } 845 846 } 847 848 protected SwipeHelper.LongPressListener getNotificationLongClicker() { 849 return new SwipeHelper.LongPressListener() { 850 @Override 851 public boolean onLongPress(View v, int x, int y) { 852 dismissPopups(); 853 854 if (!(v instanceof ExpandableNotificationRow)) { 855 return false; 856 } 857 if (v.getWindowToken() == null) { 858 Log.e(TAG, "Trying to show notification guts, but not attached to window"); 859 return false; 860 } 861 862 inflateGuts((ExpandableNotificationRow) v); 863 864 // Assume we are a status_bar_notification_row 865 final NotificationGuts guts = (NotificationGuts) v.findViewById( 866 R.id.notification_guts); 867 if (guts == null) { 868 // This view has no guts. Examples are the more card or the dismiss all view 869 return false; 870 } 871 872 // Already showing? 873 if (guts.getVisibility() == View.VISIBLE) { 874 Log.e(TAG, "Trying to show notification guts, but already visible"); 875 return false; 876 } 877 878 guts.setVisibility(View.VISIBLE); 879 final double horz = Math.max(guts.getWidth() - x, x); 880 final double vert = Math.max(guts.getActualHeight() - y, y); 881 final float r = (float) Math.hypot(horz, vert); 882 final Animator a 883 = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r); 884 a.setDuration(400); 885 a.setInterpolator(mLinearOutSlowIn); 886 a.start(); 887 888 mNotificationGutsExposed = guts; 889 890 return true; 891 } 892 }; 893 } 894 895 public void dismissPopups() { 896 if (mNotificationGutsExposed != null) { 897 final NotificationGuts v = mNotificationGutsExposed; 898 mNotificationGutsExposed = null; 899 900 if (v.getWindowToken() == null) return; 901 902 final int x = (v.getLeft() + v.getRight()) / 2; 903 final int y = (v.getTop() + v.getActualHeight() / 2); 904 final Animator a = ViewAnimationUtils.createCircularReveal(v, 905 x, y, x, 0); 906 a.setDuration(200); 907 a.setInterpolator(mFastOutLinearIn); 908 a.addListener(new AnimatorListenerAdapter() { 909 @Override 910 public void onAnimationEnd(Animator animation) { 911 super.onAnimationEnd(animation); 912 v.setVisibility(View.GONE); 913 } 914 }); 915 a.start(); 916 } 917 } 918 919 public void onHeadsUpDismissed() { 920 } 921 922 @Override 923 public void showRecentApps(boolean triggeredFromAltTab) { 924 int msg = MSG_SHOW_RECENT_APPS; 925 mHandler.removeMessages(msg); 926 mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 0).sendToTarget(); 927 } 928 929 @Override 930 public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 931 int msg = MSG_HIDE_RECENT_APPS; 932 mHandler.removeMessages(msg); 933 mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 934 triggeredFromHomeKey ? 1 : 0).sendToTarget(); 935 } 936 937 @Override 938 public void toggleRecentApps() { 939 int msg = MSG_TOGGLE_RECENTS_APPS; 940 mHandler.removeMessages(msg); 941 mHandler.sendEmptyMessage(msg); 942 } 943 944 @Override 945 public void preloadRecentApps() { 946 int msg = MSG_PRELOAD_RECENT_APPS; 947 mHandler.removeMessages(msg); 948 mHandler.sendEmptyMessage(msg); 949 } 950 951 @Override 952 public void cancelPreloadRecentApps() { 953 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 954 mHandler.removeMessages(msg); 955 mHandler.sendEmptyMessage(msg); 956 } 957 958 /** Jumps to the next affiliated task in the group. */ 959 public void showNextAffiliatedTask() { 960 int msg = MSG_SHOW_NEXT_AFFILIATED_TASK; 961 mHandler.removeMessages(msg); 962 mHandler.sendEmptyMessage(msg); 963 } 964 965 /** Jumps to the previous affiliated task in the group. */ 966 public void showPreviousAffiliatedTask() { 967 int msg = MSG_SHOW_PREV_AFFILIATED_TASK; 968 mHandler.removeMessages(msg); 969 mHandler.sendEmptyMessage(msg); 970 } 971 972 @Override 973 public void showSearchPanel() { 974 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 975 mSearchPanelView.show(true, true); 976 } 977 } 978 979 @Override 980 public void hideSearchPanel() { 981 int msg = MSG_CLOSE_SEARCH_PANEL; 982 mHandler.removeMessages(msg); 983 mHandler.sendEmptyMessage(msg); 984 } 985 986 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 987 LayoutParams layoutParams); 988 989 protected void updateSearchPanel() { 990 // Search Panel 991 boolean visible = false; 992 if (mSearchPanelView != null) { 993 visible = mSearchPanelView.isShowing(); 994 mWindowManager.removeView(mSearchPanelView); 995 } 996 997 // Provide SearchPanel with a temporary parent to allow layout params to work. 998 LinearLayout tmpRoot = new LinearLayout(mContext); 999 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 1000 R.layout.status_bar_search_panel, tmpRoot, false); 1001 mSearchPanelView.setOnTouchListener( 1002 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 1003 mSearchPanelView.setVisibility(View.GONE); 1004 boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical(); 1005 mSearchPanelView.setHorizontal(vertical); 1006 1007 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 1008 1009 mWindowManager.addView(mSearchPanelView, lp); 1010 mSearchPanelView.setBar(this); 1011 if (visible) { 1012 mSearchPanelView.show(true, false); 1013 } 1014 } 1015 1016 protected H createHandler() { 1017 return new H(); 1018 } 1019 1020 static void sendCloseSystemWindows(Context context, String reason) { 1021 if (ActivityManagerNative.isSystemReady()) { 1022 try { 1023 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 1024 } catch (RemoteException e) { 1025 } 1026 } 1027 } 1028 1029 protected abstract View getStatusBarView(); 1030 1031 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 1032 // additional optimization when we have software system buttons - start loading the recent 1033 // tasks on touch down 1034 @Override 1035 public boolean onTouch(View v, MotionEvent event) { 1036 int action = event.getAction() & MotionEvent.ACTION_MASK; 1037 if (action == MotionEvent.ACTION_DOWN) { 1038 preloadRecents(); 1039 } else if (action == MotionEvent.ACTION_CANCEL) { 1040 cancelPreloadingRecents(); 1041 } else if (action == MotionEvent.ACTION_UP) { 1042 if (!v.isPressed()) { 1043 cancelPreloadingRecents(); 1044 } 1045 1046 } 1047 return false; 1048 } 1049 }; 1050 1051 /** Proxy for RecentsComponent */ 1052 1053 protected void showRecents(boolean triggeredFromAltTab) { 1054 if (mRecents != null) { 1055 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 1056 mRecents.showRecents(triggeredFromAltTab, getStatusBarView()); 1057 } 1058 } 1059 1060 protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 1061 if (mRecents != null) { 1062 mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey); 1063 } 1064 } 1065 1066 protected void toggleRecents() { 1067 if (mRecents != null) { 1068 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 1069 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView()); 1070 } 1071 } 1072 1073 protected void preloadRecents() { 1074 if (mRecents != null) { 1075 mRecents.preloadRecents(); 1076 } 1077 } 1078 1079 protected void cancelPreloadingRecents() { 1080 if (mRecents != null) { 1081 mRecents.cancelPreloadingRecents(); 1082 } 1083 } 1084 1085 protected void showRecentsNextAffiliatedTask() { 1086 if (mRecents != null) { 1087 mRecents.showNextAffiliatedTask(); 1088 } 1089 } 1090 1091 protected void showRecentsPreviousAffiliatedTask() { 1092 if (mRecents != null) { 1093 mRecents.showPrevAffiliatedTask(); 1094 } 1095 } 1096 1097 @Override 1098 public void onVisibilityChanged(boolean visible) { 1099 // Do nothing 1100 } 1101 1102 public abstract void resetHeadsUpDecayTimer(); 1103 1104 public abstract void scheduleHeadsUpOpen(); 1105 1106 public abstract void scheduleHeadsUpClose(); 1107 1108 public abstract void scheduleHeadsUpEscalation(); 1109 1110 /** 1111 * Save the current "public" (locked and secure) state of the lockscreen. 1112 */ 1113 public void setLockscreenPublicMode(boolean publicMode) { 1114 mLockscreenPublicMode = publicMode; 1115 } 1116 1117 public boolean isLockscreenPublicMode() { 1118 return mLockscreenPublicMode; 1119 } 1120 1121 /** 1122 * Has the given user chosen to allow their private (full) notifications to be shown even 1123 * when the lockscreen is in "public" (secure & locked) mode? 1124 */ 1125 public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { 1126 if (userHandle == UserHandle.USER_ALL) { 1127 return true; 1128 } 1129 1130 if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { 1131 final boolean allowed = 0 != Settings.Secure.getIntForUser( 1132 mContext.getContentResolver(), 1133 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); 1134 final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, 1135 userHandle); 1136 final boolean allowedByDpm = (dpmFlags 1137 & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; 1138 mUsersAllowingPrivateNotifications.append(userHandle, allowed && allowedByDpm); 1139 return allowed; 1140 } 1141 1142 return mUsersAllowingPrivateNotifications.get(userHandle); 1143 } 1144 1145 /** 1146 * Returns true if we're on a secure lockscreen and the user wants to hide "sensitive" 1147 * notification data. If so, private notifications should show their (possibly 1148 * auto-generated) publicVersion, and secret notifications should be totally invisible. 1149 */ 1150 @Override // NotificationData.Environment 1151 public boolean shouldHideSensitiveContents(int userid) { 1152 return isLockscreenPublicMode() && !userAllowsPrivateNotificationsInPublic(userid); 1153 } 1154 1155 public void onNotificationClear(StatusBarNotification notification) { 1156 try { 1157 mBarService.onNotificationClear( 1158 notification.getPackageName(), 1159 notification.getTag(), 1160 notification.getId(), 1161 notification.getUserId()); 1162 } catch (android.os.RemoteException ex) { 1163 // oh well 1164 } 1165 } 1166 1167 protected class H extends Handler { 1168 public void handleMessage(Message m) { 1169 switch (m.what) { 1170 case MSG_SHOW_RECENT_APPS: 1171 showRecents(m.arg1 > 0); 1172 break; 1173 case MSG_HIDE_RECENT_APPS: 1174 hideRecents(m.arg1 > 0, m.arg2 > 0); 1175 break; 1176 case MSG_TOGGLE_RECENTS_APPS: 1177 toggleRecents(); 1178 break; 1179 case MSG_PRELOAD_RECENT_APPS: 1180 preloadRecents(); 1181 break; 1182 case MSG_CANCEL_PRELOAD_RECENT_APPS: 1183 cancelPreloadingRecents(); 1184 break; 1185 case MSG_SHOW_NEXT_AFFILIATED_TASK: 1186 showRecentsNextAffiliatedTask(); 1187 break; 1188 case MSG_SHOW_PREV_AFFILIATED_TASK: 1189 showRecentsPreviousAffiliatedTask(); 1190 break; 1191 case MSG_CLOSE_SEARCH_PANEL: 1192 if (DEBUG) Log.d(TAG, "closing search panel"); 1193 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 1194 mSearchPanelView.show(false, true); 1195 } 1196 break; 1197 } 1198 } 1199 } 1200 1201 public class TouchOutsideListener implements View.OnTouchListener { 1202 private int mMsg; 1203 private StatusBarPanel mPanel; 1204 1205 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1206 mMsg = msg; 1207 mPanel = panel; 1208 } 1209 1210 public boolean onTouch(View v, MotionEvent ev) { 1211 final int action = ev.getAction(); 1212 if (action == MotionEvent.ACTION_OUTSIDE 1213 || (action == MotionEvent.ACTION_DOWN 1214 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1215 mHandler.removeMessages(mMsg); 1216 mHandler.sendEmptyMessage(mMsg); 1217 return true; 1218 } 1219 return false; 1220 } 1221 } 1222 1223 protected void workAroundBadLayerDrawableOpacity(View v) { 1224 } 1225 1226 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 1227 return inflateViews(entry, parent, false); 1228 } 1229 1230 protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { 1231 return inflateViews(entry, parent, true); 1232 } 1233 1234 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { 1235 PackageManager pmUser = getPackageManagerForUser( 1236 entry.notification.getUser().getIdentifier()); 1237 1238 int maxHeight = mRowMaxHeight; 1239 final StatusBarNotification sbn = entry.notification; 1240 RemoteViews contentView = sbn.getNotification().contentView; 1241 RemoteViews bigContentView = sbn.getNotification().bigContentView; 1242 1243 if (isHeadsUp) { 1244 maxHeight = 1245 mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height); 1246 bigContentView = sbn.getNotification().headsUpContentView; 1247 } 1248 1249 if (contentView == null) { 1250 return false; 1251 } 1252 1253 if (DEBUG) { 1254 Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion); 1255 } 1256 1257 Notification publicNotification = sbn.getNotification().publicVersion; 1258 1259 ExpandableNotificationRow row; 1260 1261 // Stash away previous user expansion state so we can restore it at 1262 // the end. 1263 boolean hasUserChangedExpansion = false; 1264 boolean userExpanded = false; 1265 boolean userLocked = false; 1266 1267 if (entry.row != null) { 1268 row = entry.row; 1269 hasUserChangedExpansion = row.hasUserChangedExpansion(); 1270 userExpanded = row.isUserExpanded(); 1271 userLocked = row.isUserLocked(); 1272 entry.reset(); 1273 if (hasUserChangedExpansion) { 1274 row.setUserExpanded(userExpanded); 1275 } 1276 } else { 1277 // create the row view 1278 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( 1279 Context.LAYOUT_INFLATER_SERVICE); 1280 row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row, 1281 parent, false); 1282 row.setExpansionLogger(this, entry.notification.getKey()); 1283 } 1284 1285 workAroundBadLayerDrawableOpacity(row); 1286 View vetoButton = updateNotificationVetoButton(row, sbn); 1287 vetoButton.setContentDescription(mContext.getString( 1288 R.string.accessibility_remove_notification)); 1289 1290 // NB: the large icon is now handled entirely by the template 1291 1292 // bind the click event to the content area 1293 NotificationContentView expanded = 1294 (NotificationContentView) row.findViewById(R.id.expanded); 1295 NotificationContentView expandedPublic = 1296 (NotificationContentView) row.findViewById(R.id.expandedPublic); 1297 1298 row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1299 1300 PendingIntent contentIntent = sbn.getNotification().contentIntent; 1301 if (contentIntent != null) { 1302 final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(), 1303 isHeadsUp); 1304 row.setOnClickListener(listener); 1305 } else { 1306 row.setOnClickListener(null); 1307 } 1308 1309 // set up the adaptive layout 1310 View contentViewLocal = null; 1311 View bigContentViewLocal = null; 1312 try { 1313 contentViewLocal = contentView.apply(mContext, expanded, 1314 mOnClickHandler); 1315 if (bigContentView != null) { 1316 bigContentViewLocal = bigContentView.apply(mContext, expanded, 1317 mOnClickHandler); 1318 } 1319 } 1320 catch (RuntimeException e) { 1321 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 1322 Log.e(TAG, "couldn't inflate view for notification " + ident, e); 1323 return false; 1324 } 1325 1326 if (contentViewLocal != null) { 1327 contentViewLocal.setIsRootNamespace(true); 1328 expanded.setContractedChild(contentViewLocal); 1329 } 1330 if (bigContentViewLocal != null) { 1331 bigContentViewLocal.setIsRootNamespace(true); 1332 expanded.setExpandedChild(bigContentViewLocal); 1333 } 1334 1335 // now the public version 1336 View publicViewLocal = null; 1337 if (publicNotification != null) { 1338 try { 1339 publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic, 1340 mOnClickHandler); 1341 1342 if (publicViewLocal != null) { 1343 publicViewLocal.setIsRootNamespace(true); 1344 expandedPublic.setContractedChild(publicViewLocal); 1345 } 1346 } 1347 catch (RuntimeException e) { 1348 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 1349 Log.e(TAG, "couldn't inflate public view for notification " + ident, e); 1350 publicViewLocal = null; 1351 } 1352 } 1353 1354 // Extract target SDK version. 1355 try { 1356 ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); 1357 entry.targetSdk = info.targetSdkVersion; 1358 } catch (NameNotFoundException ex) { 1359 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 1360 } 1361 1362 if (publicViewLocal == null) { 1363 // Add a basic notification template 1364 publicViewLocal = LayoutInflater.from(mContext).inflate( 1365 R.layout.notification_public_default, 1366 expandedPublic, false); 1367 publicViewLocal.setIsRootNamespace(true); 1368 expandedPublic.setContractedChild(publicViewLocal); 1369 1370 final TextView title = (TextView) publicViewLocal.findViewById(R.id.title); 1371 try { 1372 title.setText(pmUser.getApplicationLabel( 1373 pmUser.getApplicationInfo(entry.notification.getPackageName(), 0))); 1374 } catch (NameNotFoundException e) { 1375 title.setText(entry.notification.getPackageName()); 1376 } 1377 1378 final ImageView icon = (ImageView) publicViewLocal.findViewById(R.id.icon); 1379 final ImageView profileBadge = (ImageView) publicViewLocal.findViewById( 1380 R.id.profile_badge_line3); 1381 1382 final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(), 1383 entry.notification.getUser(), 1384 entry.notification.getNotification().icon, 1385 entry.notification.getNotification().iconLevel, 1386 entry.notification.getNotification().number, 1387 entry.notification.getNotification().tickerText); 1388 1389 Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic); 1390 icon.setImageDrawable(iconDrawable); 1391 if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP 1392 || mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) { 1393 icon.setBackgroundResource( 1394 com.android.internal.R.drawable.notification_icon_legacy_bg); 1395 int padding = mContext.getResources().getDimensionPixelSize( 1396 com.android.internal.R.dimen.notification_large_icon_circle_padding); 1397 icon.setPadding(padding, padding, padding, padding); 1398 if (sbn.getNotification().color != Notification.COLOR_DEFAULT) { 1399 icon.getBackground().setColorFilter( 1400 sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP); 1401 } 1402 } 1403 1404 if (profileBadge != null) { 1405 Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity( 1406 entry.notification.getUser(), 0); 1407 if (profileDrawable != null) { 1408 profileBadge.setImageDrawable(profileDrawable); 1409 profileBadge.setVisibility(View.VISIBLE); 1410 } else { 1411 profileBadge.setVisibility(View.GONE); 1412 } 1413 } 1414 1415 final View privateTime = contentViewLocal.findViewById(com.android.internal.R.id.time); 1416 final DateTimeView time = (DateTimeView) publicViewLocal.findViewById(R.id.time); 1417 if (privateTime != null && privateTime.getVisibility() == View.VISIBLE) { 1418 time.setVisibility(View.VISIBLE); 1419 time.setTime(entry.notification.getNotification().when); 1420 } 1421 1422 final TextView text = (TextView) publicViewLocal.findViewById(R.id.text); 1423 if (text != null) { 1424 text.setText(R.string.notification_hidden_text); 1425 text.setTextAppearance(mContext, 1426 R.style.TextAppearance_Material_Notification_Parenthetical); 1427 } 1428 1429 int topPadding = Notification.Builder.calculateTopPadding(mContext, 1430 false /* hasThreeLines */, 1431 mContext.getResources().getConfiguration().fontScale); 1432 title.setPadding(0, topPadding, 0, 0); 1433 1434 entry.autoRedacted = true; 1435 } 1436 1437 row.setClearable(sbn.isClearable()); 1438 1439 if (MULTIUSER_DEBUG) { 1440 TextView debug = (TextView) row.findViewById(R.id.debug_info); 1441 if (debug != null) { 1442 debug.setVisibility(View.VISIBLE); 1443 debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId()); 1444 } 1445 } 1446 entry.row = row; 1447 entry.row.setHeightRange(mRowMinHeight, maxHeight); 1448 entry.row.setOnActivatedListener(this); 1449 entry.expanded = contentViewLocal; 1450 entry.expandedPublic = publicViewLocal; 1451 entry.setBigContentView(bigContentViewLocal); 1452 1453 applyColorsAndBackgrounds(sbn, entry); 1454 1455 // Restore previous flags. 1456 if (hasUserChangedExpansion) { 1457 // Note: setUserExpanded() conveniently ignores calls with 1458 // userExpanded=true if !isExpandable(). 1459 row.setUserExpanded(userExpanded); 1460 } 1461 row.setUserLocked(userLocked); 1462 row.setStatusBarNotification(entry.notification); 1463 return true; 1464 } 1465 1466 public NotificationClicker makeClicker(PendingIntent intent, String notificationKey, 1467 boolean forHun) { 1468 return new NotificationClicker(intent, notificationKey, forHun); 1469 } 1470 1471 protected class NotificationClicker implements View.OnClickListener { 1472 private PendingIntent mIntent; 1473 private final String mNotificationKey; 1474 private boolean mIsHeadsUp; 1475 1476 public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) { 1477 mIntent = intent; 1478 mNotificationKey = notificationKey; 1479 mIsHeadsUp = forHun; 1480 } 1481 1482 public void onClick(final View v) { 1483 final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); 1484 final boolean afterKeyguardGone = mIntent.isActivity() 1485 && PreviewInflater.wouldLaunchResolverActivity(mContext, mIntent.getIntent(), 1486 mCurrentUserId); 1487 dismissKeyguardThenExecute(new OnDismissAction() { 1488 public boolean onDismiss() { 1489 if (mIsHeadsUp) { 1490 mHeadsUpNotificationView.clear(); 1491 } 1492 new Thread() { 1493 @Override 1494 public void run() { 1495 try { 1496 if (keyguardShowing && !afterKeyguardGone) { 1497 ActivityManagerNative.getDefault() 1498 .keyguardWaitingForActivityDrawn(); 1499 } 1500 1501 // The intent we are sending is for the application, which 1502 // won't have permission to immediately start an activity after 1503 // the user switches to home. We know it is safe to do at this 1504 // point, so make sure new activity switches are now allowed. 1505 ActivityManagerNative.getDefault().resumeAppSwitches(); 1506 } catch (RemoteException e) { 1507 } 1508 1509 if (mIntent != null) { 1510 try { 1511 mIntent.send(); 1512 } catch (PendingIntent.CanceledException e) { 1513 // the stack trace isn't very helpful here. 1514 // Just log the exception message. 1515 Log.w(TAG, "Sending contentIntent failed: " + e); 1516 1517 // TODO: Dismiss Keyguard. 1518 } 1519 if (mIntent.isActivity()) { 1520 overrideActivityPendingAppTransition(keyguardShowing 1521 && !afterKeyguardGone); 1522 } 1523 } 1524 1525 try { 1526 mBarService.onNotificationClick(mNotificationKey); 1527 } catch (RemoteException ex) { 1528 // system process is dead if we're here. 1529 } 1530 } 1531 }.start(); 1532 1533 // close the shade if it was open 1534 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 1535 visibilityChanged(false); 1536 1537 return mIntent != null && mIntent.isActivity(); 1538 } 1539 }, afterKeyguardGone); 1540 } 1541 } 1542 1543 public void animateCollapsePanels(int flags, boolean force) { 1544 } 1545 1546 public void overrideActivityPendingAppTransition(boolean keyguardShowing) { 1547 if (keyguardShowing) { 1548 try { 1549 mWindowManagerService.overridePendingAppTransition(null, 0, 0, null); 1550 } catch (RemoteException e) { 1551 Log.w(TAG, "Error overriding app transition: " + e); 1552 } 1553 } 1554 } 1555 1556 /** 1557 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 1558 * This was added last-minute and is inconsistent with the way the rest of the notifications 1559 * are handled, because the notification isn't really cancelled. The lights are just 1560 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1561 * this is what he wants. (see bug 1131461) 1562 */ 1563 protected void visibilityChanged(boolean visible) { 1564 if (mPanelSlightlyVisible != visible) { 1565 mPanelSlightlyVisible = visible; 1566 if (!visible) { 1567 dismissPopups(); 1568 } 1569 try { 1570 if (visible) { 1571 mBarService.onPanelRevealed(); 1572 } else { 1573 mBarService.onPanelHidden(); 1574 } 1575 } catch (RemoteException ex) { 1576 // Won't fail unless the world has ended. 1577 } 1578 } 1579 } 1580 1581 /** 1582 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 1583 * about the failure. 1584 * 1585 * WARNING: this will call back into us. Don't hold any locks. 1586 */ 1587 void handleNotificationError(StatusBarNotification n, String message) { 1588 removeNotification(n.getKey(), null); 1589 try { 1590 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), 1591 n.getInitialPid(), message, n.getUserId()); 1592 } catch (RemoteException ex) { 1593 // The end is nigh. 1594 } 1595 } 1596 1597 protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { 1598 NotificationData.Entry entry = mNotificationData.remove(key, ranking); 1599 if (entry == null) { 1600 Log.w(TAG, "removeNotification for unknown key: " + key); 1601 return null; 1602 } 1603 updateNotifications(); 1604 return entry.notification; 1605 } 1606 1607 protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) { 1608 if (DEBUG) { 1609 Log.d(TAG, "createNotificationViews(notification=" + sbn); 1610 } 1611 // Construct the icon. 1612 Notification n = sbn.getNotification(); 1613 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1614 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n); 1615 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1616 1617 final StatusBarIcon ic = new StatusBarIcon(sbn.getPackageName(), 1618 sbn.getUser(), 1619 n.icon, 1620 n.iconLevel, 1621 n.number, 1622 n.tickerText); 1623 if (!iconView.set(ic)) { 1624 handleNotificationError(sbn, "Couldn't create icon: " + ic); 1625 return null; 1626 } 1627 // Construct the expanded view. 1628 NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView); 1629 if (!inflateViews(entry, mStackScroller)) { 1630 handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn); 1631 return null; 1632 } 1633 return entry; 1634 } 1635 1636 protected void addNotificationViews(Entry entry, RankingMap ranking) { 1637 if (entry == null) { 1638 return; 1639 } 1640 // Add the expanded view and icon. 1641 mNotificationData.add(entry, ranking); 1642 updateNotifications(); 1643 } 1644 1645 /** 1646 * @return The number of notifications we show on Keyguard. 1647 */ 1648 protected abstract int getMaxKeyguardNotifications(); 1649 1650 /** 1651 * Updates expanded, dimmed and locked states of notification rows. 1652 */ 1653 protected void updateRowStates() { 1654 int maxKeyguardNotifications = getMaxKeyguardNotifications(); 1655 mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); 1656 1657 ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); 1658 final int N = activeNotifications.size(); 1659 1660 int visibleNotifications = 0; 1661 boolean onKeyguard = mState == StatusBarState.KEYGUARD; 1662 for (int i = 0; i < N; i++) { 1663 NotificationData.Entry entry = activeNotifications.get(i); 1664 if (onKeyguard) { 1665 entry.row.setExpansionDisabled(true); 1666 } else { 1667 entry.row.setExpansionDisabled(false); 1668 if (!entry.row.isUserLocked()) { 1669 boolean top = (i == 0); 1670 entry.row.setSystemExpanded(top); 1671 } 1672 } 1673 boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); 1674 if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) || 1675 (onKeyguard && (visibleNotifications >= maxKeyguardNotifications 1676 || !showOnKeyguard))) { 1677 entry.row.setVisibility(View.GONE); 1678 if (onKeyguard && showOnKeyguard) { 1679 mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); 1680 } 1681 } else { 1682 boolean wasGone = entry.row.getVisibility() == View.GONE; 1683 entry.row.setVisibility(View.VISIBLE); 1684 if (wasGone) { 1685 // notify the scroller of a child addition 1686 mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */); 1687 } 1688 visibleNotifications++; 1689 } 1690 } 1691 1692 if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { 1693 mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE); 1694 } else { 1695 mKeyguardIconOverflowContainer.setVisibility(View.GONE); 1696 } 1697 1698 mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, 1699 mStackScroller.getChildCount() - 3); 1700 mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2); 1701 mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1); 1702 } 1703 1704 private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { 1705 return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey()); 1706 } 1707 1708 protected void setZenMode(int mode) { 1709 if (!isDeviceProvisioned()) return; 1710 mZenMode = mode; 1711 updateNotifications(); 1712 } 1713 1714 // extended in PhoneStatusBar 1715 protected void setShowLockscreenNotifications(boolean show) { 1716 mShowLockscreenNotifications = show; 1717 } 1718 1719 private void updateLockscreenNotificationSetting() { 1720 final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(), 1721 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1722 1, 1723 mCurrentUserId) != 0; 1724 final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( 1725 null /* admin */, mCurrentUserId); 1726 final boolean allowedByDpm = (dpmFlags 1727 & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; 1728 setShowLockscreenNotifications(show && allowedByDpm); 1729 } 1730 1731 protected abstract void haltTicker(); 1732 protected abstract void setAreThereNotifications(); 1733 protected abstract void updateNotifications(); 1734 protected abstract void tick(StatusBarNotification n, boolean firstTime); 1735 protected abstract void updateExpandedViewPos(int expandedPosition); 1736 protected abstract boolean shouldDisableNavbarGestures(); 1737 1738 public abstract void addNotification(StatusBarNotification notification, 1739 RankingMap ranking); 1740 protected abstract void updateNotificationRanking(RankingMap ranking); 1741 public abstract void removeNotification(String key, RankingMap ranking); 1742 1743 public void updateNotification(StatusBarNotification notification, RankingMap ranking) { 1744 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); 1745 1746 final String key = notification.getKey(); 1747 boolean wasHeadsUp = isHeadsUp(key); 1748 Entry oldEntry; 1749 if (wasHeadsUp) { 1750 oldEntry = mHeadsUpNotificationView.getEntry(); 1751 } else { 1752 oldEntry = mNotificationData.get(key); 1753 } 1754 if (oldEntry == null) { 1755 return; 1756 } 1757 1758 final StatusBarNotification oldNotification = oldEntry.notification; 1759 1760 // XXX: modify when we do something more intelligent with the two content views 1761 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 1762 Notification n = notification.getNotification(); 1763 final RemoteViews contentView = n.contentView; 1764 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 1765 final RemoteViews bigContentView = n.bigContentView; 1766 final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView; 1767 final RemoteViews headsUpContentView = n.headsUpContentView; 1768 final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; 1769 final RemoteViews oldPublicContentView = oldPublicNotification != null 1770 ? oldPublicNotification.contentView : null; 1771 final Notification publicNotification = n.publicVersion; 1772 final RemoteViews publicContentView = publicNotification != null 1773 ? publicNotification.contentView : null; 1774 1775 if (DEBUG) { 1776 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 1777 + " ongoing=" + oldNotification.isOngoing() 1778 + " expanded=" + oldEntry.expanded 1779 + " contentView=" + oldContentView 1780 + " bigContentView=" + oldBigContentView 1781 + " publicView=" + oldPublicContentView 1782 + " rowParent=" + oldEntry.row.getParent()); 1783 Log.d(TAG, "new notification: when=" + n.when 1784 + " ongoing=" + oldNotification.isOngoing() 1785 + " contentView=" + contentView 1786 + " bigContentView=" + bigContentView 1787 + " publicView=" + publicContentView); 1788 } 1789 1790 // Can we just reapply the RemoteViews in place? 1791 1792 // 1U is never null 1793 boolean contentsUnchanged = oldEntry.expanded != null 1794 && contentView.getPackage() != null 1795 && oldContentView.getPackage() != null 1796 && oldContentView.getPackage().equals(contentView.getPackage()) 1797 && oldContentView.getLayoutId() == contentView.getLayoutId(); 1798 // large view may be null 1799 boolean bigContentsUnchanged = 1800 (oldEntry.getBigContentView() == null && bigContentView == null) 1801 || ((oldEntry.getBigContentView() != null && bigContentView != null) 1802 && bigContentView.getPackage() != null 1803 && oldBigContentView.getPackage() != null 1804 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 1805 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 1806 boolean headsUpContentsUnchanged = 1807 (oldHeadsUpContentView == null && headsUpContentView == null) 1808 || ((oldHeadsUpContentView != null && headsUpContentView != null) 1809 && headsUpContentView.getPackage() != null 1810 && oldHeadsUpContentView.getPackage() != null 1811 && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) 1812 && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); 1813 boolean publicUnchanged = 1814 (oldPublicContentView == null && publicContentView == null) 1815 || ((oldPublicContentView != null && publicContentView != null) 1816 && publicContentView.getPackage() != null 1817 && oldPublicContentView.getPackage() != null 1818 && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) 1819 && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); 1820 boolean updateTicker = n.tickerText != null 1821 && !TextUtils.equals(n.tickerText, 1822 oldEntry.notification.getNotification().tickerText); 1823 1824 final boolean shouldInterrupt = shouldInterrupt(notification); 1825 final boolean alertAgain = alertAgain(oldEntry, n); 1826 boolean updateSuccessful = false; 1827 if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged 1828 && publicUnchanged) { 1829 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); 1830 oldEntry.notification = notification; 1831 try { 1832 if (oldEntry.icon != null) { 1833 // Update the icon 1834 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1835 notification.getUser(), 1836 n.icon, 1837 n.iconLevel, 1838 n.number, 1839 n.tickerText); 1840 oldEntry.icon.setNotification(n); 1841 if (!oldEntry.icon.set(ic)) { 1842 handleNotificationError(notification, "Couldn't update icon: " + ic); 1843 return; 1844 } 1845 } 1846 1847 if (wasHeadsUp) { 1848 if (shouldInterrupt) { 1849 updateHeadsUpViews(oldEntry, notification); 1850 if (alertAgain) { 1851 resetHeadsUpDecayTimer(); 1852 } 1853 } else { 1854 // we updated the notification above, so release to build a new shade entry 1855 mHeadsUpNotificationView.releaseAndClose(); 1856 return; 1857 } 1858 } else { 1859 if (shouldInterrupt && alertAgain) { 1860 removeNotificationViews(key, ranking); 1861 addNotification(notification, ranking); //this will pop the headsup 1862 } else { 1863 updateNotificationViews(oldEntry, notification); 1864 } 1865 } 1866 mNotificationData.updateRanking(ranking); 1867 updateNotifications(); 1868 updateSuccessful = true; 1869 } 1870 catch (RuntimeException e) { 1871 // It failed to add cleanly. Log, and remove the view from the panel. 1872 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1873 } 1874 } 1875 if (!updateSuccessful) { 1876 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); 1877 if (wasHeadsUp) { 1878 if (shouldInterrupt) { 1879 if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key); 1880 Entry newEntry = new Entry(notification, null); 1881 ViewGroup holder = mHeadsUpNotificationView.getHolder(); 1882 if (inflateViewsForHeadsUp(newEntry, holder)) { 1883 mHeadsUpNotificationView.showNotification(newEntry); 1884 if (alertAgain) { 1885 resetHeadsUpDecayTimer(); 1886 } 1887 } else { 1888 Log.w(TAG, "Couldn't create new updated headsup for package " 1889 + contentView.getPackage()); 1890 } 1891 } else { 1892 if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key); 1893 oldEntry.notification = notification; 1894 mHeadsUpNotificationView.releaseAndClose(); 1895 return; 1896 } 1897 } else { 1898 if (shouldInterrupt && alertAgain) { 1899 if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key); 1900 removeNotificationViews(key, ranking); 1901 addNotification(notification, ranking); //this will pop the headsup 1902 } else { 1903 if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key); 1904 oldEntry.notification = notification; 1905 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1906 notification.getUser(), 1907 n.icon, 1908 n.iconLevel, 1909 n.number, 1910 n.tickerText); 1911 oldEntry.icon.setNotification(n); 1912 oldEntry.icon.set(ic); 1913 inflateViews(oldEntry, mStackScroller, wasHeadsUp); 1914 mNotificationData.updateRanking(ranking); 1915 updateNotifications(); 1916 } 1917 } 1918 } 1919 1920 // Update the veto button accordingly (and as a result, whether this row is 1921 // swipe-dismissable) 1922 updateNotificationVetoButton(oldEntry.row, notification); 1923 1924 // Is this for you? 1925 boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); 1926 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1927 1928 // Restart the ticker if it's still running 1929 if (updateTicker && isForCurrentUser) { 1930 haltTicker(); 1931 tick(notification, false); 1932 } 1933 1934 // Recalculate the position of the sliding windows and the titles. 1935 setAreThereNotifications(); 1936 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1937 } 1938 1939 private void updateNotificationViews(NotificationData.Entry entry, 1940 StatusBarNotification notification) { 1941 updateNotificationViews(entry, notification, false); 1942 } 1943 1944 private void updateHeadsUpViews(NotificationData.Entry entry, 1945 StatusBarNotification notification) { 1946 updateNotificationViews(entry, notification, true); 1947 } 1948 1949 private void updateNotificationViews(NotificationData.Entry entry, 1950 StatusBarNotification notification, boolean isHeadsUp) { 1951 final RemoteViews contentView = notification.getNotification().contentView; 1952 final RemoteViews bigContentView = isHeadsUp 1953 ? notification.getNotification().headsUpContentView 1954 : notification.getNotification().bigContentView; 1955 final Notification publicVersion = notification.getNotification().publicVersion; 1956 final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView 1957 : null; 1958 1959 // Reapply the RemoteViews 1960 contentView.reapply(mContext, entry.expanded, mOnClickHandler); 1961 if (bigContentView != null && entry.getBigContentView() != null) { 1962 bigContentView.reapply(mContext, entry.getBigContentView(), 1963 mOnClickHandler); 1964 } 1965 if (publicContentView != null && entry.getPublicContentView() != null) { 1966 publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); 1967 } 1968 // update the contentIntent 1969 final PendingIntent contentIntent = notification.getNotification().contentIntent; 1970 if (contentIntent != null) { 1971 final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(), 1972 isHeadsUp); 1973 entry.row.setOnClickListener(listener); 1974 } else { 1975 entry.row.setOnClickListener(null); 1976 } 1977 entry.row.setStatusBarNotification(notification); 1978 entry.row.notifyContentUpdated(); 1979 entry.row.resetHeight(); 1980 } 1981 1982 protected void notifyHeadsUpScreenOn(boolean screenOn) { 1983 if (!screenOn) { 1984 scheduleHeadsUpEscalation(); 1985 } 1986 } 1987 1988 private boolean alertAgain(Entry oldEntry, Notification newNotification) { 1989 return oldEntry == null || !oldEntry.hasInterrupted() 1990 || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; 1991 } 1992 1993 protected boolean shouldInterrupt(StatusBarNotification sbn) { 1994 if (mNotificationData.shouldFilterOut(sbn)) { 1995 if (DEBUG) { 1996 Log.d(TAG, "Skipping HUN check for " + sbn.getKey() + " since it's filtered out."); 1997 } 1998 return false; 1999 } 2000 2001 Notification notification = sbn.getNotification(); 2002 // some predicates to make the boolean logic legible 2003 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 2004 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 2005 || notification.sound != null 2006 || notification.vibrate != null; 2007 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 2008 boolean isFullscreen = notification.fullScreenIntent != null; 2009 boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText); 2010 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 2011 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 2012 boolean accessibilityForcesLaunch = isFullscreen 2013 && mAccessibilityManager.isTouchExplorationEnabled(); 2014 2015 final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext); 2016 boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker))) 2017 && isAllowed 2018 && !accessibilityForcesLaunch 2019 && mPowerManager.isScreenOn() 2020 && !keyguard.isShowingAndNotOccluded() 2021 && !keyguard.isInputRestricted(); 2022 try { 2023 interrupt = interrupt && !mDreamManager.isDreaming(); 2024 } catch (RemoteException e) { 2025 Log.d(TAG, "failed to query dream manager", e); 2026 } 2027 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 2028 return interrupt; 2029 } 2030 2031 public boolean inKeyguardRestrictedInputMode() { 2032 return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted(); 2033 } 2034 2035 public void setInteracting(int barWindow, boolean interacting) { 2036 // hook for subclasses 2037 } 2038 2039 public void setBouncerShowing(boolean bouncerShowing) { 2040 mBouncerShowing = bouncerShowing; 2041 } 2042 2043 /** 2044 * @return Whether the security bouncer from Keyguard is showing. 2045 */ 2046 public boolean isBouncerShowing() { 2047 return mBouncerShowing; 2048 } 2049 2050 public void destroy() { 2051 if (mSearchPanelView != null) { 2052 mWindowManager.removeViewImmediate(mSearchPanelView); 2053 } 2054 mContext.unregisterReceiver(mBroadcastReceiver); 2055 try { 2056 mNotificationListener.unregisterAsSystemService(); 2057 } catch (RemoteException e) { 2058 // Ignore. 2059 } 2060 } 2061 2062 /** 2063 * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then 2064 * return PackageManager for mContext 2065 */ 2066 protected PackageManager getPackageManagerForUser(int userId) { 2067 Context contextForUser = mContext; 2068 // UserHandle defines special userId as negative values, e.g. USER_ALL 2069 if (userId >= 0) { 2070 try { 2071 // Create a context for the correct user so if a package isn't installed 2072 // for user 0 we can still load information about the package. 2073 contextForUser = 2074 mContext.createPackageContextAsUser(mContext.getPackageName(), 2075 Context.CONTEXT_RESTRICTED, 2076 new UserHandle(userId)); 2077 } catch (NameNotFoundException e) { 2078 // Shouldn't fail to find the package name for system ui. 2079 } 2080 } 2081 return contextForUser.getPackageManager(); 2082 } 2083 2084 @Override 2085 public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { 2086 try { 2087 mBarService.onNotificationExpansionChanged(key, userAction, expanded); 2088 } catch (RemoteException e) { 2089 // Ignore. 2090 } 2091 } 2092 } 2093