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