BaseStatusBar.java revision 98edc951712823dbf5db2b7e9c203a0e98fc616b
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 public 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 notificationUserId == UserHandle.USER_ALL 297 || thisUserId == notificationUserId; 298 } 299 300 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 301 View vetoButton = row.findViewById(R.id.veto); 302 if (n.isClearable()) { 303 final String _pkg = n.pkg; 304 final String _tag = n.tag; 305 final int _id = n.id; 306 vetoButton.setOnClickListener(new View.OnClickListener() { 307 public void onClick(View v) { 308 try { 309 mBarService.onNotificationClear(_pkg, _tag, _id); 310 } catch (RemoteException ex) { 311 // system process is dead if we're here. 312 } 313 } 314 }); 315 vetoButton.setVisibility(View.VISIBLE); 316 } else { 317 vetoButton.setVisibility(View.GONE); 318 } 319 return vetoButton; 320 } 321 322 323 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 324 if (sbn.notification.contentView.getLayoutId() != 325 com.android.internal.R.layout.notification_template_base) { 326 int version = 0; 327 try { 328 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); 329 version = info.targetSdkVersion; 330 } catch (NameNotFoundException ex) { 331 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); 332 } 333 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 334 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 335 } else { 336 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg); 337 } 338 } 339 } 340 341 private void startApplicationDetailsActivity(String packageName) { 342 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 343 Uri.fromParts("package", packageName, null)); 344 intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); 345 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities( 346 null, UserHandle.CURRENT); 347 } 348 349 protected View.OnLongClickListener getNotificationLongClicker() { 350 return new View.OnLongClickListener() { 351 @Override 352 public boolean onLongClick(View v) { 353 final String packageNameF = (String) v.getTag(); 354 if (packageNameF == null) return false; 355 if (v.getWindowToken() == null) return false; 356 mNotificationBlamePopup = new PopupMenu(mContext, v); 357 mNotificationBlamePopup.getMenuInflater().inflate( 358 R.menu.notification_popup_menu, 359 mNotificationBlamePopup.getMenu()); 360 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 361 public boolean onMenuItemClick(MenuItem item) { 362 if (item.getItemId() == R.id.notification_inspect_item) { 363 startApplicationDetailsActivity(packageNameF); 364 animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE); 365 } else { 366 return false; 367 } 368 return true; 369 } 370 }); 371 mNotificationBlamePopup.show(); 372 373 return true; 374 } 375 }; 376 } 377 378 public void dismissPopups() { 379 if (mNotificationBlamePopup != null) { 380 mNotificationBlamePopup.dismiss(); 381 mNotificationBlamePopup = null; 382 } 383 } 384 385 public void dismissIntruder() { 386 // pass 387 } 388 389 @Override 390 public void toggleRecentApps() { 391 int msg = MSG_TOGGLE_RECENTS_PANEL; 392 mHandler.removeMessages(msg); 393 mHandler.sendEmptyMessage(msg); 394 } 395 396 @Override 397 public void preloadRecentApps() { 398 int msg = MSG_PRELOAD_RECENT_APPS; 399 mHandler.removeMessages(msg); 400 mHandler.sendEmptyMessage(msg); 401 } 402 403 @Override 404 public void cancelPreloadRecentApps() { 405 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 406 mHandler.removeMessages(msg); 407 mHandler.sendEmptyMessage(msg); 408 } 409 410 @Override 411 public void showSearchPanel() { 412 int msg = MSG_OPEN_SEARCH_PANEL; 413 mHandler.removeMessages(msg); 414 mHandler.sendEmptyMessage(msg); 415 } 416 417 @Override 418 public void hideSearchPanel() { 419 int msg = MSG_CLOSE_SEARCH_PANEL; 420 mHandler.removeMessages(msg); 421 mHandler.sendEmptyMessage(msg); 422 } 423 424 protected abstract WindowManager.LayoutParams getRecentsLayoutParams( 425 LayoutParams layoutParams); 426 427 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 428 LayoutParams layoutParams); 429 430 protected RecentTasksLoader getRecentTasksLoader() { 431 final SystemUIApplication app = (SystemUIApplication) ((Service) mContext).getApplication(); 432 return app.getRecentTasksLoader(); 433 } 434 435 protected void updateSearchPanel() { 436 // Search Panel 437 boolean visible = false; 438 if (mSearchPanelView != null) { 439 visible = mSearchPanelView.isShowing(); 440 mWindowManager.removeView(mSearchPanelView); 441 } 442 443 // Provide SearchPanel with a temporary parent to allow layout params to work. 444 LinearLayout tmpRoot = new LinearLayout(mContext); 445 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 446 R.layout.status_bar_search_panel, tmpRoot, false); 447 mSearchPanelView.setOnTouchListener( 448 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 449 mSearchPanelView.setVisibility(View.GONE); 450 451 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 452 453 mWindowManager.addView(mSearchPanelView, lp); 454 mSearchPanelView.setBar(this); 455 if (visible) { 456 mSearchPanelView.show(true, false); 457 } 458 } 459 460 protected H createHandler() { 461 return new H(); 462 } 463 464 static void sendCloseSystemWindows(Context context, String reason) { 465 if (ActivityManagerNative.isSystemReady()) { 466 try { 467 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 468 } catch (RemoteException e) { 469 } 470 } 471 } 472 473 protected abstract View getStatusBarView(); 474 475 protected void toggleRecentsActivity() { 476 try { 477 final RecentTasksLoader recentTasksLoader = getRecentTasksLoader(); 478 TaskDescription firstTask = recentTasksLoader.getFirstTask(); 479 480 Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT); 481 intent.setClassName("com.android.systemui", 482 "com.android.systemui.recent.RecentsActivity"); 483 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 484 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 485 486 if (firstTask == null) { 487 mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 488 } else { 489 Bitmap first = firstTask.getThumbnail(); 490 final Resources res = mContext.getResources(); 491 492 float thumbWidth = res 493 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width); 494 float thumbHeight = res 495 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height); 496 if (first == null) { 497 throw new RuntimeException("Recents thumbnail is null"); 498 } 499 if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) { 500 first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight, 501 true); 502 if (first == null) { 503 throw new RuntimeException("Recents thumbnail is null"); 504 } 505 } 506 507 508 DisplayMetrics dm = new DisplayMetrics(); 509 mDisplay.getMetrics(dm); 510 // calculate it here, but consider moving it elsewhere 511 // first, determine which orientation you're in. 512 // todo: move the system_bar layouts to sw600dp ? 513 final Configuration config = res.getConfiguration(); 514 int x, y; 515 516 if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { 517 float appLabelLeftMargin = res 518 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_left_margin); 519 float appLabelWidth = res 520 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_width); 521 float thumbLeftMargin = res 522 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_left_margin); 523 float thumbBgPadding = res 524 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_bg_padding); 525 526 float width = appLabelLeftMargin + 527 +appLabelWidth 528 + thumbLeftMargin 529 + thumbWidth 530 + 2 * thumbBgPadding; 531 532 x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth 533 + thumbBgPadding + thumbLeftMargin); 534 y = (int) (dm.heightPixels 535 - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height) - thumbBgPadding); 536 } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 537 float thumbTopMargin = res 538 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_top_margin); 539 float thumbBgPadding = res 540 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_bg_padding); 541 float textPadding = res 542 .getDimensionPixelSize(R.dimen.status_bar_recents_text_description_padding); 543 float labelTextSize = res 544 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_text_size); 545 Paint p = new Paint(); 546 p.setTextSize(labelTextSize); 547 float labelTextHeight = p.getFontMetricsInt().bottom 548 - p.getFontMetricsInt().top; 549 float descriptionTextSize = res 550 .getDimensionPixelSize(R.dimen.status_bar_recents_app_description_text_size); 551 p.setTextSize(labelTextSize); 552 float descriptionTextHeight = p.getFontMetricsInt().bottom 553 - p.getFontMetricsInt().top; 554 555 float statusBarHeight = res 556 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 557 float recentsItemTopPadding = statusBarHeight; 558 559 float height = thumbTopMargin 560 + thumbHeight 561 + 2 * thumbBgPadding + textPadding + labelTextHeight 562 + recentsItemTopPadding + textPadding + descriptionTextHeight; 563 float recentsItemRightPadding = res 564 .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding); 565 float recentsScrollViewRightPadding = res 566 .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin); 567 x = (int) (dm.widthPixels - res 568 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width) 569 - thumbBgPadding - recentsItemRightPadding - recentsScrollViewRightPadding); 570 y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin 571 + recentsItemTopPadding + thumbBgPadding + statusBarHeight); 572 } 573 574 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation( 575 getStatusBarView(), 576 first, x, y, 577 null); 578 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 579 UserHandle.USER_CURRENT)); 580 } 581 return; 582 } catch (ActivityNotFoundException e) { 583 Log.e(TAG, "Failed to launch RecentAppsIntent", e); 584 } 585 } 586 587 protected class H extends Handler { 588 public void handleMessage(Message m) { 589 switch (m.what) { 590 case MSG_TOGGLE_RECENTS_PANEL: 591 if (DEBUG) Slog.d(TAG, "toggle recents panel"); 592 toggleRecentsActivity(); 593 break; 594 case MSG_CLOSE_RECENTS_PANEL: 595 if (DEBUG) Slog.d(TAG, "closing recents panel"); 596 Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT); 597 intent.setPackage("com.android.systemui"); 598 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 599 break; 600 case MSG_PRELOAD_RECENT_APPS: 601 if (DEBUG) Slog.d(TAG, "preloading recents"); 602 getRecentTasksLoader().preloadRecentTasksList(); 603 break; 604 case MSG_CANCEL_PRELOAD_RECENT_APPS: 605 if (DEBUG) Slog.d(TAG, "cancel preloading recents"); 606 getRecentTasksLoader().cancelPreloadingRecentTasksList(); 607 break; 608 case MSG_OPEN_SEARCH_PANEL: 609 if (DEBUG) Slog.d(TAG, "opening search panel"); 610 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 611 mSearchPanelView.show(true, true); 612 } 613 break; 614 case MSG_CLOSE_SEARCH_PANEL: 615 if (DEBUG) Slog.d(TAG, "closing search panel"); 616 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 617 mSearchPanelView.show(false, true); 618 } 619 break; 620 } 621 } 622 } 623 624 public class TouchOutsideListener implements View.OnTouchListener { 625 private int mMsg; 626 private StatusBarPanel mPanel; 627 628 public TouchOutsideListener(int msg, StatusBarPanel panel) { 629 mMsg = msg; 630 mPanel = panel; 631 } 632 633 public boolean onTouch(View v, MotionEvent ev) { 634 final int action = ev.getAction(); 635 if (action == MotionEvent.ACTION_OUTSIDE 636 || (action == MotionEvent.ACTION_DOWN 637 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 638 mHandler.removeMessages(mMsg); 639 mHandler.sendEmptyMessage(mMsg); 640 return true; 641 } 642 return false; 643 } 644 } 645 646 protected void workAroundBadLayerDrawableOpacity(View v) { 647 } 648 649 protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 650 int minHeight = 651 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 652 int maxHeight = 653 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 654 StatusBarNotification sbn = entry.notification; 655 RemoteViews oneU = sbn.notification.contentView; 656 RemoteViews large = sbn.notification.bigContentView; 657 if (oneU == null) { 658 return false; 659 } 660 661 // create the row view 662 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 663 Context.LAYOUT_INFLATER_SERVICE); 664 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 665 666 // for blaming (see SwipeHelper.setLongPressListener) 667 row.setTag(sbn.pkg); 668 669 workAroundBadLayerDrawableOpacity(row); 670 View vetoButton = updateNotificationVetoButton(row, sbn); 671 vetoButton.setContentDescription(mContext.getString( 672 R.string.accessibility_remove_notification)); 673 674 // NB: the large icon is now handled entirely by the template 675 676 // bind the click event to the content area 677 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 678 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive); 679 680 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 681 682 PendingIntent contentIntent = sbn.notification.contentIntent; 683 if (contentIntent != null) { 684 final View.OnClickListener listener = new NotificationClicker(contentIntent, 685 sbn.pkg, sbn.tag, sbn.id); 686 content.setOnClickListener(listener); 687 } else { 688 content.setOnClickListener(null); 689 } 690 691 // TODO(cwren) normalize variable names with those in updateNotification 692 View expandedOneU = null; 693 View expandedLarge = null; 694 try { 695 expandedOneU = oneU.apply(mContext, adaptive, mOnClickHandler); 696 if (large != null) { 697 expandedLarge = large.apply(mContext, adaptive, mOnClickHandler); 698 } 699 } 700 catch (RuntimeException e) { 701 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 702 Slog.e(TAG, "couldn't inflate view for notification " + ident, e); 703 return false; 704 } 705 706 if (expandedOneU != null) { 707 SizeAdaptiveLayout.LayoutParams params = 708 new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams()); 709 params.minHeight = minHeight; 710 params.maxHeight = minHeight; 711 adaptive.addView(expandedOneU, params); 712 } 713 if (expandedLarge != null) { 714 SizeAdaptiveLayout.LayoutParams params = 715 new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams()); 716 params.minHeight = minHeight+1; 717 params.maxHeight = maxHeight; 718 adaptive.addView(expandedLarge, params); 719 } 720 row.setDrawingCacheEnabled(true); 721 722 applyLegacyRowBackground(sbn, content); 723 724 row.setTag(R.id.expandable_tag, Boolean.valueOf(large != null)); 725 726 if (MULTIUSER_DEBUG) { 727 TextView debug = (TextView) row.findViewById(R.id.debug_info); 728 if (debug != null) { 729 debug.setVisibility(View.VISIBLE); 730 debug.setText("U " + entry.notification.getUserId()); 731 } 732 } 733 entry.row = row; 734 entry.content = content; 735 entry.expanded = expandedOneU; 736 entry.setLargeView(expandedLarge); 737 738 return true; 739 } 740 741 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 742 return new NotificationClicker(intent, pkg, tag, id); 743 } 744 745 private class NotificationClicker implements View.OnClickListener { 746 private PendingIntent mIntent; 747 private String mPkg; 748 private String mTag; 749 private int mId; 750 751 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 752 mIntent = intent; 753 mPkg = pkg; 754 mTag = tag; 755 mId = id; 756 } 757 758 public void onClick(View v) { 759 try { 760 // The intent we are sending is for the application, which 761 // won't have permission to immediately start an activity after 762 // the user switches to home. We know it is safe to do at this 763 // point, so make sure new activity switches are now allowed. 764 ActivityManagerNative.getDefault().resumeAppSwitches(); 765 // Also, notifications can be launched from the lock screen, 766 // so dismiss the lock screen when the activity starts. 767 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 768 } catch (RemoteException e) { 769 } 770 771 if (mIntent != null) { 772 int[] pos = new int[2]; 773 v.getLocationOnScreen(pos); 774 Intent overlay = new Intent(); 775 overlay.setSourceBounds( 776 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 777 try { 778 mIntent.send(mContext, 0, overlay); 779 } catch (PendingIntent.CanceledException e) { 780 // the stack trace isn't very helpful here. Just log the exception message. 781 Slog.w(TAG, "Sending contentIntent failed: " + e); 782 } 783 784 KeyguardManager kgm = 785 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 786 if (kgm != null) kgm.exitKeyguardSecurely(null); 787 } 788 789 try { 790 mBarService.onNotificationClick(mPkg, mTag, mId); 791 } catch (RemoteException ex) { 792 // system process is dead if we're here. 793 } 794 795 // close the shade if it was open 796 animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE); 797 visibilityChanged(false); 798 799 // If this click was on the intruder alert, hide that instead 800// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 801 } 802 } 803 /** 804 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 805 * This was added last-minute and is inconsistent with the way the rest of the notifications 806 * are handled, because the notification isn't really cancelled. The lights are just 807 * turned off. If any other notifications happen, the lights will turn back on. Steve says 808 * this is what he wants. (see bug 1131461) 809 */ 810 protected void visibilityChanged(boolean visible) { 811 if (mPanelSlightlyVisible != visible) { 812 mPanelSlightlyVisible = visible; 813 try { 814 mBarService.onPanelRevealed(); 815 } catch (RemoteException ex) { 816 // Won't fail unless the world has ended. 817 } 818 } 819 } 820 821 /** 822 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 823 * about the failure. 824 * 825 * WARNING: this will call back into us. Don't hold any locks. 826 */ 827 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 828 removeNotification(key); 829 try { 830 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 831 } catch (RemoteException ex) { 832 // The end is nigh. 833 } 834 } 835 836 protected StatusBarNotification removeNotificationViews(IBinder key) { 837 NotificationData.Entry entry = mNotificationData.remove(key); 838 if (entry == null) { 839 Slog.w(TAG, "removeNotification for unknown key: " + key); 840 return null; 841 } 842 // Remove the expanded view. 843 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 844 if (rowParent != null) rowParent.removeView(entry.row); 845 updateExpansionStates(); 846 updateNotificationIcons(); 847 848 return entry.notification; 849 } 850 851 protected StatusBarIconView addNotificationViews(IBinder key, 852 StatusBarNotification notification) { 853 if (DEBUG) { 854 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 855 } 856 // Construct the icon. 857 final StatusBarIconView iconView = new StatusBarIconView(mContext, 858 notification.pkg + "/0x" + Integer.toHexString(notification.id), 859 notification.notification); 860 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 861 862 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 863 notification.user, 864 notification.notification.icon, 865 notification.notification.iconLevel, 866 notification.notification.number, 867 notification.notification.tickerText); 868 if (!iconView.set(ic)) { 869 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 870 return null; 871 } 872 // Construct the expanded view. 873 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 874 if (!inflateViews(entry, mPile)) { 875 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 876 + notification); 877 return null; 878 } 879 880 // Add the expanded view and icon. 881 int pos = mNotificationData.add(entry); 882 if (DEBUG) { 883 Slog.d(TAG, "addNotificationViews: added at " + pos); 884 } 885 updateExpansionStates(); 886 updateNotificationIcons(); 887 888 return iconView; 889 } 890 891 protected boolean expandView(NotificationData.Entry entry, boolean expand) { 892 int rowHeight = 893 mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height); 894 ViewGroup.LayoutParams lp = entry.row.getLayoutParams(); 895 if (entry.expandable() && expand) { 896 if (DEBUG) Slog.d(TAG, "setting expanded row height to WRAP_CONTENT"); 897 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 898 } else { 899 if (DEBUG) Slog.d(TAG, "setting collapsed row height to " + rowHeight); 900 lp.height = rowHeight; 901 } 902 entry.row.setLayoutParams(lp); 903 return expand; 904 } 905 906 protected void updateExpansionStates() { 907 int N = mNotificationData.size(); 908 for (int i = 0; i < N; i++) { 909 NotificationData.Entry entry = mNotificationData.get(i); 910 if (!entry.userLocked()) { 911 if (i == (N-1)) { 912 if (DEBUG) Slog.d(TAG, "expanding top notification at " + i); 913 expandView(entry, true); 914 } else { 915 if (!entry.userExpanded()) { 916 if (DEBUG) Slog.d(TAG, "collapsing notification at " + i); 917 expandView(entry, false); 918 } else { 919 if (DEBUG) Slog.d(TAG, "ignoring user-modified notification at " + i); 920 } 921 } 922 } else { 923 if (DEBUG) Slog.d(TAG, "ignoring notification being held by user at " + i); 924 } 925 } 926 } 927 928 protected abstract void haltTicker(); 929 protected abstract void setAreThereNotifications(); 930 protected abstract void updateNotificationIcons(); 931 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 932 protected abstract void updateExpandedViewPos(int expandedPosition); 933 protected abstract int getExpandedViewMaxHeight(); 934 protected abstract boolean shouldDisableNavbarGestures(); 935 936 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 937 return parent != null && parent.indexOfChild(entry.row) == 0; 938 } 939 940 public void updateNotification(IBinder key, StatusBarNotification notification) { 941 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 942 943 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 944 if (oldEntry == null) { 945 Slog.w(TAG, "updateNotification for unknown key: " + key); 946 return; 947 } 948 949 final StatusBarNotification oldNotification = oldEntry.notification; 950 951 // XXX: modify when we do something more intelligent with the two content views 952 final RemoteViews oldContentView = oldNotification.notification.contentView; 953 final RemoteViews contentView = notification.notification.contentView; 954 final RemoteViews oldBigContentView = oldNotification.notification.bigContentView; 955 final RemoteViews bigContentView = notification.notification.bigContentView; 956 957 if (DEBUG) { 958 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 959 + " ongoing=" + oldNotification.isOngoing() 960 + " expanded=" + oldEntry.expanded 961 + " contentView=" + oldContentView 962 + " bigContentView=" + oldBigContentView 963 + " rowParent=" + oldEntry.row.getParent()); 964 Slog.d(TAG, "new notification: when=" + notification.notification.when 965 + " ongoing=" + oldNotification.isOngoing() 966 + " contentView=" + contentView 967 + " bigContentView=" + bigContentView); 968 } 969 970 // Can we just reapply the RemoteViews in place? If when didn't change, the order 971 // didn't change. 972 973 // 1U is never null 974 boolean contentsUnchanged = oldEntry.expanded != null 975 && contentView.getPackage() != null 976 && oldContentView.getPackage() != null 977 && oldContentView.getPackage().equals(contentView.getPackage()) 978 && oldContentView.getLayoutId() == contentView.getLayoutId(); 979 // large view may be null 980 boolean bigContentsUnchanged = 981 (oldEntry.getLargeView() == null && bigContentView == null) 982 || ((oldEntry.getLargeView() != null && bigContentView != null) 983 && bigContentView.getPackage() != null 984 && oldBigContentView.getPackage() != null 985 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 986 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 987 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 988 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 989 && notification.score == oldNotification.score; 990 // score now encompasses/supersedes isOngoing() 991 992 boolean updateTicker = notification.notification.tickerText != null 993 && !TextUtils.equals(notification.notification.tickerText, 994 oldEntry.notification.notification.tickerText); 995 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 996 if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) { 997 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 998 oldEntry.notification = notification; 999 try { 1000 // Reapply the RemoteViews 1001 contentView.reapply(mContext, oldEntry.expanded, mOnClickHandler); 1002 if (bigContentView != null && oldEntry.getLargeView() != null) { 1003 bigContentView.reapply(mContext, oldEntry.getLargeView(), mOnClickHandler); 1004 } 1005 // update the contentIntent 1006 final PendingIntent contentIntent = notification.notification.contentIntent; 1007 if (contentIntent != null) { 1008 final View.OnClickListener listener = makeClicker(contentIntent, 1009 notification.pkg, notification.tag, notification.id); 1010 oldEntry.content.setOnClickListener(listener); 1011 } else { 1012 oldEntry.content.setOnClickListener(null); 1013 } 1014 // Update the icon. 1015 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 1016 notification.user, 1017 notification.notification.icon, notification.notification.iconLevel, 1018 notification.notification.number, 1019 notification.notification.tickerText); 1020 if (!oldEntry.icon.set(ic)) { 1021 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 1022 return; 1023 } 1024 updateExpansionStates(); 1025 } 1026 catch (RuntimeException e) { 1027 // It failed to add cleanly. Log, and remove the view from the panel. 1028 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1029 removeNotificationViews(key); 1030 addNotificationViews(key, notification); 1031 } 1032 } else { 1033 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 1034 if (DEBUG) Slog.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 1035 if (DEBUG) Slog.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); 1036 if (DEBUG) Slog.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); 1037 final boolean wasExpanded = oldEntry.userExpanded(); 1038 removeNotificationViews(key); 1039 addNotificationViews(key, notification); 1040 if (wasExpanded) { 1041 final NotificationData.Entry newEntry = mNotificationData.findByKey(key); 1042 expandView(newEntry, true); 1043 newEntry.setUserExpanded(true); 1044 } 1045 } 1046 1047 // Update the veto button accordingly (and as a result, whether this row is 1048 // swipe-dismissable) 1049 updateNotificationVetoButton(oldEntry.row, notification); 1050 1051 // Is this for you? 1052 boolean isForCurrentUser = notificationIsForCurrentUser(notification); 1053 if (DEBUG) Slog.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1054 1055 // Restart the ticker if it's still running 1056 if (updateTicker && isForCurrentUser) { 1057 haltTicker(); 1058 tick(key, notification, false); 1059 } 1060 1061 // Recalculate the position of the sliding windows and the titles. 1062 setAreThereNotifications(); 1063 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1064 1065 // See if we need to update the intruder. 1066 if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) { 1067 if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification); 1068 // XXX: this is a hack for Alarms. The real implementation will need to *update* 1069 // the intruder. 1070 if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add() 1071 if (DEBUG) Slog.d(TAG, "no longer intrudes!"); 1072 mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 1073 } 1074 } 1075 } 1076 1077 // Q: What kinds of notifications should show during setup? 1078 // A: Almost none! Only things coming from the system (package is "android") that also 1079 // have special "kind" tags marking them as relevant for setup (see below). 1080 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 1081 if ("android".equals(sbn.pkg)) { 1082 if (sbn.notification.kind != null) { 1083 for (String aKind : sbn.notification.kind) { 1084 // IME switcher, created by InputMethodManagerService 1085 if ("android.system.imeswitcher".equals(aKind)) return true; 1086 // OTA availability & errors, created by SystemUpdateService 1087 if ("android.system.update".equals(aKind)) return true; 1088 } 1089 } 1090 } 1091 return false; 1092 } 1093 1094 public boolean inKeyguardRestrictedInputMode() { 1095 KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 1096 return km.inKeyguardRestrictedInputMode(); 1097 } 1098} 1099