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