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