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