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