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