BaseStatusBar.java revision f6e83f452f2223dc99da157f95a11a584f94ea50
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar; 18 19import android.app.ActivityManager; 20import android.app.ActivityManagerNative; 21import android.app.KeyguardManager; 22import android.app.Notification; 23import android.app.PendingIntent; 24import android.app.TaskStackBuilder; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.pm.ApplicationInfo; 30import android.content.pm.PackageManager.NameNotFoundException; 31import android.content.res.Configuration; 32import android.database.ContentObserver; 33import android.graphics.Rect; 34import android.net.Uri; 35import android.os.Build; 36import android.os.Handler; 37import android.os.IBinder; 38import android.os.Message; 39import android.os.PowerManager; 40import android.os.RemoteException; 41import android.os.ServiceManager; 42import android.os.UserHandle; 43import android.provider.Settings; 44import android.service.dreams.DreamService; 45import android.service.dreams.IDreamManager; 46import android.service.notification.StatusBarNotification; 47import android.text.TextUtils; 48import android.util.Log; 49import android.view.Display; 50import android.view.IWindowManager; 51import android.view.LayoutInflater; 52import android.view.MenuItem; 53import android.view.MotionEvent; 54import android.view.View; 55import android.view.ViewGroup; 56import android.view.ViewGroup.LayoutParams; 57import android.view.WindowManager; 58import android.view.WindowManagerGlobal; 59import android.widget.ImageView; 60import android.widget.LinearLayout; 61import android.widget.PopupMenu; 62import android.widget.RemoteViews; 63import android.widget.TextView; 64 65import com.android.internal.statusbar.IStatusBarService; 66import com.android.internal.statusbar.StatusBarIcon; 67import com.android.internal.statusbar.StatusBarIconList; 68import com.android.internal.widget.SizeAdaptiveLayout; 69import com.android.systemui.R; 70import com.android.systemui.RecentsComponent; 71import com.android.systemui.SearchPanelView; 72import com.android.systemui.SystemUI; 73import com.android.systemui.statusbar.policy.NotificationRowLayout; 74 75import java.util.ArrayList; 76import java.util.Locale; 77 78public abstract class BaseStatusBar extends SystemUI implements 79 CommandQueue.Callbacks { 80 public static final String TAG = "StatusBar"; 81 public static final boolean DEBUG = false; 82 public static final boolean MULTIUSER_DEBUG = false; 83 84 protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020; 85 protected static final int MSG_CLOSE_RECENTS_PANEL = 1021; 86 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 87 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 88 protected static final int MSG_OPEN_SEARCH_PANEL = 1024; 89 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025; 90 protected static final int MSG_SHOW_HEADS_UP = 1026; 91 protected static final int MSG_HIDE_HEADS_UP = 1027; 92 protected static final int MSG_ESCALATE_HEADS_UP = 1028; 93 94 protected static final boolean ENABLE_HEADS_UP = true; 95 // scores above this threshold should be displayed in heads up mode. 96 protected static final int INTERRUPTION_THRESHOLD = 11; 97 protected static final String SETTING_HEADS_UP = "heads_up_enabled"; 98 99 // Should match the value in PhoneWindowManager 100 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 101 102 public static final int EXPANDED_LEAVE_ALONE = -10000; 103 public static final int EXPANDED_FULL_OPEN = -10001; 104 105 protected CommandQueue mCommandQueue; 106 protected IStatusBarService mBarService; 107 protected H mHandler = createHandler(); 108 109 // all notifications 110 protected NotificationData mNotificationData = new NotificationData(); 111 protected NotificationRowLayout mPile; 112 113 protected NotificationData.Entry mInterruptingNotificationEntry; 114 protected long mInterruptingNotificationTime; 115 116 // used to notify status bar for suppressing notification LED 117 protected boolean mPanelSlightlyVisible; 118 119 // Search panel 120 protected SearchPanelView mSearchPanelView; 121 122 protected PopupMenu mNotificationBlamePopup; 123 124 protected int mCurrentUserId = 0; 125 126 protected int mLayoutDirection; 127 private Locale mLocale; 128 protected boolean mUseHeadsUp = false; 129 130 protected IDreamManager mDreamManager; 131 KeyguardManager mKeyguardManager; 132 PowerManager mPowerManager; 133 protected int mRowHeight; 134 135 // UI-specific methods 136 137 /** 138 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 139 * and add them to the window manager. 140 */ 141 protected abstract void createAndAddWindows(); 142 143 protected WindowManager mWindowManager; 144 protected IWindowManager mWindowManagerService; 145 protected abstract void refreshLayout(int layoutDirection); 146 147 protected Display mDisplay; 148 149 private boolean mDeviceProvisioned = false; 150 151 private RecentsComponent mRecents; 152 153 public IStatusBarService getStatusBarService() { 154 return mBarService; 155 } 156 157 public boolean isDeviceProvisioned() { 158 return mDeviceProvisioned; 159 } 160 161 private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) { 162 @Override 163 public void onChange(boolean selfChange) { 164 final boolean provisioned = 0 != Settings.Global.getInt( 165 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); 166 if (provisioned != mDeviceProvisioned) { 167 mDeviceProvisioned = provisioned; 168 updateNotificationIcons(); 169 } 170 } 171 }; 172 173 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { 174 @Override 175 public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { 176 if (DEBUG) { 177 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); 178 } 179 final boolean isActivity = pendingIntent.isActivity(); 180 if (isActivity) { 181 try { 182 // The intent we are sending is for the application, which 183 // won't have permission to immediately start an activity after 184 // the user switches to home. We know it is safe to do at this 185 // point, so make sure new activity switches are now allowed. 186 ActivityManagerNative.getDefault().resumeAppSwitches(); 187 // Also, notifications can be launched from the lock screen, 188 // so dismiss the lock screen when the activity starts. 189 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 190 } catch (RemoteException e) { 191 } 192 } 193 194 boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent); 195 196 if (isActivity && handled) { 197 // close the shade if it was open 198 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 199 visibilityChanged(false); 200 } 201 return handled; 202 } 203 }; 204 205 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 206 @Override 207 public void onReceive(Context context, Intent intent) { 208 String action = intent.getAction(); 209 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 210 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 211 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); 212 userSwitched(mCurrentUserId); 213 } 214 } 215 }; 216 217 public void start() { 218 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 219 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 220 mDisplay = mWindowManager.getDefaultDisplay(); 221 222 mDreamManager = IDreamManager.Stub.asInterface( 223 ServiceManager.checkService(DreamService.DREAM_SERVICE)); 224 mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 225 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 226 227 mProvisioningObserver.onChange(false); // set up 228 mContext.getContentResolver().registerContentObserver( 229 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, 230 mProvisioningObserver); 231 232 mBarService = IStatusBarService.Stub.asInterface( 233 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 234 235 mRecents = getComponent(RecentsComponent.class); 236 237 mLocale = mContext.getResources().getConfiguration().locale; 238 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 239 240 // Connect in to the status bar manager service 241 StatusBarIconList iconList = new StatusBarIconList(); 242 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); 243 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 244 mCommandQueue = new CommandQueue(this, iconList); 245 246 int[] switches = new int[7]; 247 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 248 try { 249 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, 250 switches, binders); 251 } catch (RemoteException ex) { 252 // If the system process isn't there we're doomed anyway. 253 } 254 255 createAndAddWindows(); 256 257 disable(switches[0]); 258 setSystemUiVisibility(switches[1], 0xffffffff); 259 topAppWindowChanged(switches[2] != 0); 260 // StatusBarManagerService has a back up of IME token and it's restored here. 261 setImeWindowStatus(binders.get(0), switches[3], switches[4]); 262 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 263 264 // Set up the initial icon state 265 int N = iconList.size(); 266 int viewIndex = 0; 267 for (int i=0; i<N; i++) { 268 StatusBarIcon icon = iconList.getIcon(i); 269 if (icon != null) { 270 addIcon(iconList.getSlot(i), i, viewIndex, icon); 271 viewIndex++; 272 } 273 } 274 275 // Set up the initial notification state 276 N = notificationKeys.size(); 277 if (N == notifications.size()) { 278 for (int i=0; i<N; i++) { 279 addNotification(notificationKeys.get(i), notifications.get(i)); 280 } 281 } else { 282 Log.wtf(TAG, "Notification list length mismatch: keys=" + N 283 + " notifications=" + notifications.size()); 284 } 285 286 if (DEBUG) { 287 Log.d(TAG, String.format( 288 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 289 iconList.size(), 290 switches[0], 291 switches[1], 292 switches[2], 293 switches[3] 294 )); 295 } 296 297 mCurrentUserId = ActivityManager.getCurrentUser(); 298 299 IntentFilter filter = new IntentFilter(); 300 filter.addAction(Intent.ACTION_USER_SWITCHED); 301 mContext.registerReceiver(mBroadcastReceiver, filter); 302 303 mLocale = mContext.getResources().getConfiguration().locale; 304 } 305 306 public void userSwitched(int newUserId) { 307 // should be overridden 308 } 309 310 public boolean notificationIsForCurrentUser(StatusBarNotification n) { 311 final int thisUserId = mCurrentUserId; 312 final int notificationUserId = n.getUserId(); 313 if (DEBUG && MULTIUSER_DEBUG) { 314 Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", 315 n, thisUserId, notificationUserId)); 316 } 317 return notificationUserId == UserHandle.USER_ALL 318 || thisUserId == notificationUserId; 319 } 320 321 @Override 322 protected void onConfigurationChanged(Configuration newConfig) { 323 final Locale newLocale = mContext.getResources().getConfiguration().locale; 324 if (! newLocale.equals(mLocale)) { 325 mLocale = newLocale; 326 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 327 refreshLayout(mLayoutDirection); 328 } 329 } 330 331 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 332 View vetoButton = row.findViewById(R.id.veto); 333 if (n.isClearable() || (mInterruptingNotificationEntry != null 334 && mInterruptingNotificationEntry.row == row)) { 335 final String _pkg = n.getPackageName(); 336 final String _tag = n.getTag(); 337 final int _id = n.getId(); 338 vetoButton.setOnClickListener(new View.OnClickListener() { 339 public void onClick(View v) { 340 // Accessibility feedback 341 v.announceForAccessibility( 342 mContext.getString(R.string.accessibility_notification_dismissed)); 343 try { 344 mBarService.onNotificationClear(_pkg, _tag, _id); 345 346 } catch (RemoteException ex) { 347 // system process is dead if we're here. 348 } 349 } 350 }); 351 vetoButton.setVisibility(View.VISIBLE); 352 } else { 353 vetoButton.setVisibility(View.GONE); 354 } 355 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 356 return vetoButton; 357 } 358 359 360 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 361 if (sbn.getNotification().contentView.getLayoutId() != 362 com.android.internal.R.layout.notification_template_base) { 363 int version = 0; 364 try { 365 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0); 366 version = info.targetSdkVersion; 367 } catch (NameNotFoundException ex) { 368 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 369 } 370 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 371 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 372 } else { 373 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg); 374 } 375 } 376 } 377 378 private void startApplicationDetailsActivity(String packageName) { 379 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 380 Uri.fromParts("package", packageName, null)); 381 intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); 382 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities( 383 null, UserHandle.CURRENT); 384 } 385 386 protected View.OnLongClickListener getNotificationLongClicker() { 387 return new View.OnLongClickListener() { 388 @Override 389 public boolean onLongClick(View v) { 390 final String packageNameF = (String) v.getTag(); 391 if (packageNameF == null) return false; 392 if (v.getWindowToken() == null) return false; 393 mNotificationBlamePopup = new PopupMenu(mContext, v); 394 mNotificationBlamePopup.getMenuInflater().inflate( 395 R.menu.notification_popup_menu, 396 mNotificationBlamePopup.getMenu()); 397 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 398 public boolean onMenuItemClick(MenuItem item) { 399 if (item.getItemId() == R.id.notification_inspect_item) { 400 startApplicationDetailsActivity(packageNameF); 401 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 402 } else { 403 return false; 404 } 405 return true; 406 } 407 }); 408 mNotificationBlamePopup.show(); 409 410 return true; 411 } 412 }; 413 } 414 415 public void dismissPopups() { 416 if (mNotificationBlamePopup != null) { 417 mNotificationBlamePopup.dismiss(); 418 mNotificationBlamePopup = null; 419 } 420 } 421 422 public void onHeadsUpDismissed() { 423 } 424 425 @Override 426 public void toggleRecentApps() { 427 int msg = MSG_TOGGLE_RECENTS_PANEL; 428 mHandler.removeMessages(msg); 429 mHandler.sendEmptyMessage(msg); 430 } 431 432 @Override 433 public void preloadRecentApps() { 434 int msg = MSG_PRELOAD_RECENT_APPS; 435 mHandler.removeMessages(msg); 436 mHandler.sendEmptyMessage(msg); 437 } 438 439 @Override 440 public void cancelPreloadRecentApps() { 441 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 442 mHandler.removeMessages(msg); 443 mHandler.sendEmptyMessage(msg); 444 } 445 446 @Override 447 public void showSearchPanel() { 448 int msg = MSG_OPEN_SEARCH_PANEL; 449 mHandler.removeMessages(msg); 450 mHandler.sendEmptyMessage(msg); 451 } 452 453 @Override 454 public void hideSearchPanel() { 455 int msg = MSG_CLOSE_SEARCH_PANEL; 456 mHandler.removeMessages(msg); 457 mHandler.sendEmptyMessage(msg); 458 } 459 460 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 461 LayoutParams layoutParams); 462 463 protected void updateSearchPanel() { 464 // Search Panel 465 boolean visible = false; 466 if (mSearchPanelView != null) { 467 visible = mSearchPanelView.isShowing(); 468 mWindowManager.removeView(mSearchPanelView); 469 } 470 471 // Provide SearchPanel with a temporary parent to allow layout params to work. 472 LinearLayout tmpRoot = new LinearLayout(mContext); 473 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 474 R.layout.status_bar_search_panel, tmpRoot, false); 475 mSearchPanelView.setOnTouchListener( 476 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 477 mSearchPanelView.setVisibility(View.GONE); 478 479 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 480 481 mWindowManager.addView(mSearchPanelView, lp); 482 mSearchPanelView.setBar(this); 483 if (visible) { 484 mSearchPanelView.show(true, false); 485 } 486 } 487 488 protected H createHandler() { 489 return new H(); 490 } 491 492 static void sendCloseSystemWindows(Context context, String reason) { 493 if (ActivityManagerNative.isSystemReady()) { 494 try { 495 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 496 } catch (RemoteException e) { 497 } 498 } 499 } 500 501 protected abstract View getStatusBarView(); 502 503 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 504 // additional optimization when we have software system buttons - start loading the recent 505 // tasks on touch down 506 @Override 507 public boolean onTouch(View v, MotionEvent event) { 508 int action = event.getAction() & MotionEvent.ACTION_MASK; 509 if (action == MotionEvent.ACTION_DOWN) { 510 preloadRecentTasksList(); 511 } else if (action == MotionEvent.ACTION_CANCEL) { 512 cancelPreloadingRecentTasksList(); 513 } else if (action == MotionEvent.ACTION_UP) { 514 if (!v.isPressed()) { 515 cancelPreloadingRecentTasksList(); 516 } 517 518 } 519 return false; 520 } 521 }; 522 523 protected void toggleRecentsActivity() { 524 if (mRecents != null) { 525 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView()); 526 } 527 } 528 529 protected void preloadRecentTasksList() { 530 if (mRecents != null) { 531 mRecents.preloadRecentTasksList(); 532 } 533 } 534 535 protected void cancelPreloadingRecentTasksList() { 536 if (mRecents != null) { 537 mRecents.cancelPreloadingRecentTasksList(); 538 } 539 } 540 541 protected void closeRecents() { 542 if (mRecents != null) { 543 mRecents.closeRecents(); 544 } 545 } 546 547 public abstract void resetHeadsUpDecayTimer(); 548 549 protected class H extends Handler { 550 public void handleMessage(Message m) { 551 Intent intent; 552 switch (m.what) { 553 case MSG_TOGGLE_RECENTS_PANEL: 554 toggleRecentsActivity(); 555 break; 556 case MSG_CLOSE_RECENTS_PANEL: 557 closeRecents(); 558 break; 559 case MSG_PRELOAD_RECENT_APPS: 560 preloadRecentTasksList(); 561 break; 562 case MSG_CANCEL_PRELOAD_RECENT_APPS: 563 cancelPreloadingRecentTasksList(); 564 break; 565 case MSG_OPEN_SEARCH_PANEL: 566 if (DEBUG) Log.d(TAG, "opening search panel"); 567 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 568 mSearchPanelView.show(true, true); 569 } 570 break; 571 case MSG_CLOSE_SEARCH_PANEL: 572 if (DEBUG) Log.d(TAG, "closing search panel"); 573 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 574 mSearchPanelView.show(false, true); 575 } 576 break; 577 } 578 } 579 } 580 581 public class TouchOutsideListener implements View.OnTouchListener { 582 private int mMsg; 583 private StatusBarPanel mPanel; 584 585 public TouchOutsideListener(int msg, StatusBarPanel panel) { 586 mMsg = msg; 587 mPanel = panel; 588 } 589 590 public boolean onTouch(View v, MotionEvent ev) { 591 final int action = ev.getAction(); 592 if (action == MotionEvent.ACTION_OUTSIDE 593 || (action == MotionEvent.ACTION_DOWN 594 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 595 mHandler.removeMessages(mMsg); 596 mHandler.sendEmptyMessage(mMsg); 597 return true; 598 } 599 return false; 600 } 601 } 602 603 protected void workAroundBadLayerDrawableOpacity(View v) { 604 } 605 606 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 607 int minHeight = 608 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 609 int maxHeight = 610 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 611 StatusBarNotification sbn = entry.notification; 612 RemoteViews contentView = sbn.getNotification().contentView; 613 RemoteViews bigContentView = sbn.getNotification().bigContentView; 614 if (contentView == null) { 615 return false; 616 } 617 618 // create the row view 619 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 620 Context.LAYOUT_INFLATER_SERVICE); 621 ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate( 622 R.layout.status_bar_notification_row, parent, false); 623 624 // for blaming (see SwipeHelper.setLongPressListener) 625 row.setTag(sbn.getPackageName()); 626 627 workAroundBadLayerDrawableOpacity(row); 628 View vetoButton = updateNotificationVetoButton(row, sbn); 629 vetoButton.setContentDescription(mContext.getString( 630 R.string.accessibility_remove_notification)); 631 632 // NB: the large icon is now handled entirely by the template 633 634 // bind the click event to the content area 635 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 636 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive); 637 638 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 639 640 PendingIntent contentIntent = sbn.getNotification().contentIntent; 641 if (contentIntent != null) { 642 final View.OnClickListener listener = new NotificationClicker(contentIntent, 643 sbn.getPackageName(), sbn.getTag(), sbn.getId()); 644 content.setOnClickListener(listener); 645 } else { 646 content.setOnClickListener(null); 647 } 648 649 View contentViewLocal = null; 650 View bigContentViewLocal = null; 651 try { 652 contentViewLocal = contentView.apply(mContext, adaptive, mOnClickHandler); 653 if (bigContentView != null) { 654 bigContentViewLocal = bigContentView.apply(mContext, adaptive, mOnClickHandler); 655 } 656 } 657 catch (RuntimeException e) { 658 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 659 Log.e(TAG, "couldn't inflate view for notification " + ident, e); 660 return false; 661 } 662 663 if (contentViewLocal != null) { 664 SizeAdaptiveLayout.LayoutParams params = 665 new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams()); 666 params.minHeight = minHeight; 667 params.maxHeight = minHeight; 668 adaptive.addView(contentViewLocal, params); 669 } 670 if (bigContentViewLocal != null) { 671 SizeAdaptiveLayout.LayoutParams params = 672 new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams()); 673 params.minHeight = minHeight+1; 674 params.maxHeight = maxHeight; 675 adaptive.addView(bigContentViewLocal, params); 676 } 677 row.setDrawingCacheEnabled(true); 678 679 applyLegacyRowBackground(sbn, content); 680 681 if (MULTIUSER_DEBUG) { 682 TextView debug = (TextView) row.findViewById(R.id.debug_info); 683 if (debug != null) { 684 debug.setVisibility(View.VISIBLE); 685 debug.setText("U " + entry.notification.getUserId()); 686 } 687 } 688 entry.row = row; 689 entry.row.setRowHeight(mRowHeight); 690 entry.content = content; 691 entry.expanded = contentViewLocal; 692 entry.setBigContentView(bigContentViewLocal); 693 694 return true; 695 } 696 697 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 698 return new NotificationClicker(intent, pkg, tag, id); 699 } 700 701 protected class NotificationClicker implements View.OnClickListener { 702 private PendingIntent mIntent; 703 private String mPkg; 704 private String mTag; 705 private int mId; 706 707 public NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 708 mIntent = intent; 709 mPkg = pkg; 710 mTag = tag; 711 mId = id; 712 } 713 714 public void onClick(View v) { 715 try { 716 // The intent we are sending is for the application, which 717 // won't have permission to immediately start an activity after 718 // the user switches to home. We know it is safe to do at this 719 // point, so make sure new activity switches are now allowed. 720 ActivityManagerNative.getDefault().resumeAppSwitches(); 721 // Also, notifications can be launched from the lock screen, 722 // so dismiss the lock screen when the activity starts. 723 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 724 } catch (RemoteException e) { 725 } 726 727 if (mIntent != null) { 728 int[] pos = new int[2]; 729 v.getLocationOnScreen(pos); 730 Intent overlay = new Intent(); 731 overlay.setSourceBounds( 732 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 733 try { 734 mIntent.send(mContext, 0, overlay); 735 } catch (PendingIntent.CanceledException e) { 736 // the stack trace isn't very helpful here. Just log the exception message. 737 Log.w(TAG, "Sending contentIntent failed: " + e); 738 } 739 740 KeyguardManager kgm = 741 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 742 if (kgm != null) kgm.exitKeyguardSecurely(null); 743 } 744 745 try { 746 mBarService.onNotificationClick(mPkg, mTag, mId); 747 } catch (RemoteException ex) { 748 // system process is dead if we're here. 749 } 750 751 // close the shade if it was open 752 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 753 visibilityChanged(false); 754 } 755 } 756 /** 757 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 758 * This was added last-minute and is inconsistent with the way the rest of the notifications 759 * are handled, because the notification isn't really cancelled. The lights are just 760 * turned off. If any other notifications happen, the lights will turn back on. Steve says 761 * this is what he wants. (see bug 1131461) 762 */ 763 protected void visibilityChanged(boolean visible) { 764 if (mPanelSlightlyVisible != visible) { 765 mPanelSlightlyVisible = visible; 766 try { 767 mBarService.onPanelRevealed(); 768 } catch (RemoteException ex) { 769 // Won't fail unless the world has ended. 770 } 771 } 772 } 773 774 /** 775 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 776 * about the failure. 777 * 778 * WARNING: this will call back into us. Don't hold any locks. 779 */ 780 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 781 removeNotification(key); 782 try { 783 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message); 784 } catch (RemoteException ex) { 785 // The end is nigh. 786 } 787 } 788 789 protected StatusBarNotification removeNotificationViews(IBinder key) { 790 NotificationData.Entry entry = mNotificationData.remove(key); 791 if (entry == null) { 792 Log.w(TAG, "removeNotification for unknown key: " + key); 793 return null; 794 } 795 // Remove the expanded view. 796 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 797 if (rowParent != null) rowParent.removeView(entry.row); 798 updateExpansionStates(); 799 updateNotificationIcons(); 800 801 return entry.notification; 802 } 803 804 protected NotificationData.Entry createNotificationViews(IBinder key, 805 StatusBarNotification notification) { 806 if (DEBUG) { 807 Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification); 808 } 809 // Construct the icon. 810 final StatusBarIconView iconView = new StatusBarIconView(mContext, 811 notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()), 812 notification.getNotification()); 813 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 814 815 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 816 notification.getUser(), 817 notification.getNotification().icon, 818 notification.getNotification().iconLevel, 819 notification.getNotification().number, 820 notification.getNotification().tickerText); 821 if (!iconView.set(ic)) { 822 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 823 return null; 824 } 825 // Construct the expanded view. 826 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 827 if (!inflateViews(entry, mPile)) { 828 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 829 + notification); 830 return null; 831 } 832 return entry; 833 } 834 835 protected void addNotificationViews(NotificationData.Entry entry) { 836 // Add the expanded view and icon. 837 int pos = mNotificationData.add(entry); 838 if (DEBUG) { 839 Log.d(TAG, "addNotificationViews: added at " + pos); 840 } 841 updateExpansionStates(); 842 updateNotificationIcons(); 843 } 844 845 private void addNotificationViews(IBinder key, StatusBarNotification notification) { 846 addNotificationViews(createNotificationViews(key, notification)); 847 } 848 849 protected void updateExpansionStates() { 850 int N = mNotificationData.size(); 851 for (int i = 0; i < N; i++) { 852 NotificationData.Entry entry = mNotificationData.get(i); 853 if (!entry.row.isUserLocked()) { 854 if (i == (N-1)) { 855 if (DEBUG) Log.d(TAG, "expanding top notification at " + i); 856 entry.row.setExpanded(true); 857 } else { 858 if (!entry.row.isUserExpanded()) { 859 if (DEBUG) Log.d(TAG, "collapsing notification at " + i); 860 entry.row.setExpanded(false); 861 } else { 862 if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i); 863 } 864 } 865 } else { 866 if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i); 867 } 868 } 869 } 870 871 protected abstract void haltTicker(); 872 protected abstract void setAreThereNotifications(); 873 protected abstract void updateNotificationIcons(); 874 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 875 protected abstract void updateExpandedViewPos(int expandedPosition); 876 protected abstract int getExpandedViewMaxHeight(); 877 protected abstract boolean shouldDisableNavbarGestures(); 878 879 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 880 return parent != null && parent.indexOfChild(entry.row) == 0; 881 } 882 883 public void updateNotification(IBinder key, StatusBarNotification notification) { 884 if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 885 886 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 887 if (oldEntry == null) { 888 Log.w(TAG, "updateNotification for unknown key: " + key); 889 return; 890 } 891 892 final StatusBarNotification oldNotification = oldEntry.notification; 893 894 // XXX: modify when we do something more intelligent with the two content views 895 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 896 final RemoteViews contentView = notification.getNotification().contentView; 897 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 898 final RemoteViews bigContentView = notification.getNotification().bigContentView; 899 900 if (DEBUG) { 901 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 902 + " ongoing=" + oldNotification.isOngoing() 903 + " expanded=" + oldEntry.expanded 904 + " contentView=" + oldContentView 905 + " bigContentView=" + oldBigContentView 906 + " rowParent=" + oldEntry.row.getParent()); 907 Log.d(TAG, "new notification: when=" + notification.getNotification().when 908 + " ongoing=" + oldNotification.isOngoing() 909 + " contentView=" + contentView 910 + " bigContentView=" + bigContentView); 911 } 912 913 // Can we just reapply the RemoteViews in place? If when didn't change, the order 914 // didn't change. 915 916 // 1U is never null 917 boolean contentsUnchanged = oldEntry.expanded != null 918 && contentView.getPackage() != null 919 && oldContentView.getPackage() != null 920 && oldContentView.getPackage().equals(contentView.getPackage()) 921 && oldContentView.getLayoutId() == contentView.getLayoutId(); 922 // large view may be null 923 boolean bigContentsUnchanged = 924 (oldEntry.getBigContentView() == null && bigContentView == null) 925 || ((oldEntry.getBigContentView() != null && bigContentView != null) 926 && bigContentView.getPackage() != null 927 && oldBigContentView.getPackage() != null 928 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 929 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 930 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 931 boolean orderUnchanged = notification.getNotification().when== oldNotification.getNotification().when 932 && notification.getScore() == oldNotification.getScore(); 933 // score now encompasses/supersedes isOngoing() 934 935 boolean updateTicker = notification.getNotification().tickerText != null 936 && !TextUtils.equals(notification.getNotification().tickerText, 937 oldEntry.notification.getNotification().tickerText); 938 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 939 if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) { 940 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); 941 oldEntry.notification = notification; 942 try { 943 updateNotificationViews(oldEntry, notification); 944 945 if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null 946 && oldNotification == mInterruptingNotificationEntry.notification) { 947 if (!shouldInterrupt(notification)) { 948 if (DEBUG) Log.d(TAG, "no longer interrupts!"); 949 mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); 950 } else { 951 if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification); 952 mInterruptingNotificationEntry.notification = notification; 953 updateNotificationViews(mInterruptingNotificationEntry, notification); 954 } 955 } 956 957 // Update the icon. 958 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 959 notification.getUser(), 960 notification.getNotification().icon, notification.getNotification().iconLevel, 961 notification.getNotification().number, 962 notification.getNotification().tickerText); 963 if (!oldEntry.icon.set(ic)) { 964 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 965 return; 966 } 967 updateExpansionStates(); 968 } 969 catch (RuntimeException e) { 970 // It failed to add cleanly. Log, and remove the view from the panel. 971 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 972 removeNotificationViews(key); 973 addNotificationViews(key, notification); 974 } 975 } else { 976 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); 977 if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 978 if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); 979 if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); 980 final boolean wasExpanded = oldEntry.row.isUserExpanded(); 981 removeNotificationViews(key); 982 addNotificationViews(key, notification); // will also replace the heads up 983 if (wasExpanded) { 984 final NotificationData.Entry newEntry = mNotificationData.findByKey(key); 985 newEntry.row.setExpanded(true); 986 newEntry.row.setUserExpanded(true); 987 } 988 } 989 990 // Update the veto button accordingly (and as a result, whether this row is 991 // swipe-dismissable) 992 updateNotificationVetoButton(oldEntry.row, notification); 993 994 // Is this for you? 995 boolean isForCurrentUser = notificationIsForCurrentUser(notification); 996 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 997 998 // Restart the ticker if it's still running 999 if (updateTicker && isForCurrentUser) { 1000 haltTicker(); 1001 tick(key, notification, false); 1002 } 1003 1004 // Recalculate the position of the sliding windows and the titles. 1005 setAreThereNotifications(); 1006 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1007 } 1008 1009 private void updateNotificationViews(NotificationData.Entry entry, 1010 StatusBarNotification notification) { 1011 final RemoteViews contentView = notification.getNotification().contentView; 1012 final RemoteViews bigContentView = notification.getNotification().bigContentView; 1013 // Reapply the RemoteViews 1014 contentView.reapply(mContext, entry.expanded, mOnClickHandler); 1015 if (bigContentView != null && entry.getBigContentView() != null) { 1016 bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler); 1017 } 1018 // update the contentIntent 1019 final PendingIntent contentIntent = notification.getNotification().contentIntent; 1020 if (contentIntent != null) { 1021 final View.OnClickListener listener = makeClicker(contentIntent, 1022 notification.getPackageName(), notification.getTag(), notification.getId()); 1023 entry.content.setOnClickListener(listener); 1024 } else { 1025 entry.content.setOnClickListener(null); 1026 } 1027 } 1028 1029 protected void notifyHeadsUpScreenOn(boolean screenOn) { 1030 if (!screenOn && mInterruptingNotificationEntry != null) { 1031 mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); 1032 } 1033 } 1034 1035 protected boolean shouldInterrupt(StatusBarNotification sbn) { 1036 Notification notification = sbn.getNotification(); 1037 // some predicates to make the boolean logic legible 1038 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 1039 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 1040 || notification.sound != null 1041 || notification.vibrate != null; 1042 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 1043 boolean isFullscreen = notification.fullScreenIntent != null; 1044 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 1045 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 1046 1047 boolean interrupt = (isFullscreen || (isHighPriority && isNoisy)) 1048 && isAllowed 1049 && mPowerManager.isScreenOn() 1050 && !mKeyguardManager.isKeyguardLocked(); 1051 try { 1052 interrupt = interrupt && !mDreamManager.isDreaming(); 1053 } catch (RemoteException e) { 1054 Log.d(TAG, "failed to query dream manager", e); 1055 } 1056 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 1057 return interrupt; 1058 } 1059 1060 // Q: What kinds of notifications should show during setup? 1061 // A: Almost none! Only things coming from the system (package is "android") that also 1062 // have special "kind" tags marking them as relevant for setup (see below). 1063 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 1064 if ("android".equals(sbn.getPackageName())) { 1065 if (sbn.getNotification().kind != null) { 1066 for (String aKind : sbn.getNotification().kind) { 1067 // IME switcher, created by InputMethodManagerService 1068 if ("android.system.imeswitcher".equals(aKind)) return true; 1069 // OTA availability & errors, created by SystemUpdateService 1070 if ("android.system.update".equals(aKind)) return true; 1071 } 1072 } 1073 } 1074 return false; 1075 } 1076 1077 public boolean inKeyguardRestrictedInputMode() { 1078 KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 1079 return km.inKeyguardRestrictedInputMode(); 1080 } 1081 1082 public void setInteracting(int barWindow, boolean interacting) { 1083 // hook for subclasses 1084 } 1085 1086 public void destroy() { 1087 if (mSearchPanelView != null) { 1088 mWindowManager.removeViewImmediate(mSearchPanelView); 1089 } 1090 mContext.unregisterReceiver(mBroadcastReceiver); 1091 } 1092} 1093