TabletStatusBar.java revision 4daaeafd278d22ec9013d1cdaade562044ee907e
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar.tablet; 18 19import java.io.FileDescriptor; 20import java.io.PrintWriter; 21import java.util.ArrayList; 22 23import android.animation.LayoutTransition; 24import android.animation.ObjectAnimator; 25import android.app.ActivityManagerNative; 26import android.app.PendingIntent; 27import android.app.Notification; 28import android.app.StatusBarManager; 29import android.content.Context; 30import android.content.Intent; 31import android.content.res.Resources; 32import android.graphics.PixelFormat; 33import android.graphics.Rect; 34import android.graphics.drawable.Drawable; 35import android.graphics.drawable.LayerDrawable; 36import android.os.Handler; 37import android.os.IBinder; 38import android.os.Message; 39import android.os.RemoteException; 40import android.text.TextUtils; 41import android.util.Slog; 42import android.view.animation.Animation; 43import android.view.animation.AnimationUtils; 44import android.view.Gravity; 45import android.view.LayoutInflater; 46import android.view.MotionEvent; 47import android.view.VelocityTracker; 48import android.view.View; 49import android.view.ViewConfiguration; 50import android.view.ViewGroup; 51import android.view.WindowManager; 52import android.view.WindowManagerImpl; 53import android.widget.FrameLayout; 54import android.widget.ImageView; 55import android.widget.LinearLayout; 56import android.widget.RemoteViews; 57import android.widget.ScrollView; 58import android.widget.TextSwitcher; 59import android.widget.TextView; 60 61import com.android.internal.statusbar.StatusBarIcon; 62import com.android.internal.statusbar.StatusBarNotification; 63 64import com.android.systemui.R; 65import com.android.systemui.statusbar.*; 66import com.android.systemui.statusbar.policy.BatteryController; 67import com.android.systemui.statusbar.policy.NetworkController; 68import com.android.systemui.recent.RecentApplicationsActivity; 69 70public class TabletStatusBar extends StatusBar { 71 public static final boolean DEBUG = false; 72 public static final String TAG = "TabletStatusBar"; 73 74 public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; 75 public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; 76 public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002; 77 public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003; 78 public static final int MSG_OPEN_RECENTS_PANEL = 1020; 79 public static final int MSG_CLOSE_RECENTS_PANEL = 1021; 80 public static final int MSG_HIDE_SHADOWS = 1030; 81 public static final int MSG_SHOW_SHADOWS = 1031; 82 public static final int MSG_RESTORE_SHADOWS = 1032; 83 84 private static final int MAX_IMAGE_LEVEL = 10000; 85 private static final boolean USE_2D_RECENTS = true; 86 87 public static final int LIGHTS_ON_DELAY = 5000; 88 89 int mIconSize; 90 91 H mHandler = new H(); 92 93 // tracking all current notifications 94 private NotificationData mNotns = new NotificationData(); 95 96 TabletStatusBarView mStatusBarView; 97 View mNotificationArea; 98 View mNotificationTrigger; 99 NotificationIconArea mNotificationIconArea; 100 View mNavigationArea; 101 102 View mBackButton; 103 View mHomeButton; 104 View mMenuButton; 105 View mRecentButton; 106 107 InputMethodButton mInputMethodButton; 108 109 NotificationPanel mNotificationPanel; 110 NotificationPeekPanel mNotificationPeekWindow; 111 ViewGroup mNotificationPeekRow; 112 int mNotificationPeekIndex; 113 LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight; 114 115 int mNotificationPeekTapDuration; 116 int mNotificationFlingVelocity; 117 118 ViewGroup mPile; 119 120 BatteryController mBatteryController; 121 NetworkController mNetworkController; 122 123 View mBarContents; 124 125 // lights out support 126 View mBackShadow, mHomeShadow, mRecentShadow, mMenuShadow, mNotificationShadow; 127 ShadowController mShadowController; 128 129 NotificationIconArea.IconLayout mIconLayout; 130 131 TabletTicker mTicker; 132 View mTickerView; 133 boolean mTicking; 134 135 // for disabling the status bar 136 int mDisabled = 0; 137 138 boolean mNotificationsOn = true; 139 private RecentAppsPanel mRecentsPanel; 140 141 protected void addPanelWindows() { 142 final Context context = mContext; 143 144 final Resources res = context.getResources(); 145 final int barHeight= res.getDimensionPixelSize( 146 com.android.internal.R.dimen.status_bar_height); 147 148 // Notification Panel 149 mNotificationPanel = (NotificationPanel)View.inflate(context, 150 R.layout.sysbar_panel_notifications, null); 151 mNotificationPanel.setVisibility(View.GONE); 152 mNotificationPanel.setOnTouchListener( 153 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel)); 154 155 // the battery and network icons 156 mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery)); 157 mBatteryController.addLabelView( 158 (TextView)mNotificationPanel.findViewById(R.id.battery_text)); 159 mNetworkController.addCombinedDataIconView( 160 (ImageView)mNotificationPanel.findViewById(R.id.network)); 161 mNetworkController.addLabelView( 162 (TextView)mNotificationPanel.findViewById(R.id.network_text)); 163 164 mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel); 165 166 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 167 ViewGroup.LayoutParams.WRAP_CONTENT, 168 ViewGroup.LayoutParams.MATCH_PARENT, 169 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 170 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 171 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 172 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 173 PixelFormat.TRANSLUCENT); 174 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 175 lp.setTitle("NotificationPanel"); 176 177 WindowManagerImpl.getDefault().addView(mNotificationPanel, lp); 178 179 // Notification preview window 180 mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context, 181 R.layout.sysbar_panel_notification_peek, null); 182 mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content); 183 mNotificationPeekWindow.setVisibility(View.GONE); 184 mNotificationPeekWindow.setOnTouchListener( 185 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPeekWindow)); 186 mNotificationPeekScrubRight = new LayoutTransition(); 187 mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, 188 ObjectAnimator.ofInt(null, "left", -512, 0)); 189 mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, 190 ObjectAnimator.ofInt(null, "left", -512, 0)); 191 mNotificationPeekScrubRight.setDuration(500); 192 193 mNotificationPeekScrubLeft = new LayoutTransition(); 194 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, 195 ObjectAnimator.ofInt(null, "left", 512, 0)); 196 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, 197 ObjectAnimator.ofInt(null, "left", 512, 0)); 198 mNotificationPeekScrubLeft.setDuration(500); 199 200 // XXX: setIgnoreChildren? 201 lp = new WindowManager.LayoutParams( 202 512, // ViewGroup.LayoutParams.WRAP_CONTENT, 203 ViewGroup.LayoutParams.WRAP_CONTENT, 204 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 205 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 206 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 207 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 208 PixelFormat.TRANSLUCENT); 209 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 210 lp.setTitle("NotificationPeekWindow"); 211 lp.windowAnimations = com.android.internal.R.style.Animation_Toast; 212 213 WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp); 214 215 // Recents Panel 216 if (USE_2D_RECENTS) { 217 mRecentsPanel = (RecentAppsPanel) View.inflate(context, R.layout.sysbar_panel_recent, 218 null); 219 mRecentsPanel.setVisibility(View.GONE); 220 mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, 221 mRecentsPanel)); 222 mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel); 223 224 lp = new WindowManager.LayoutParams( 225 ViewGroup.LayoutParams.WRAP_CONTENT, 226 ViewGroup.LayoutParams.WRAP_CONTENT, 227 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 228 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 229 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 230 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 231 PixelFormat.TRANSLUCENT); 232 lp.gravity = Gravity.BOTTOM | Gravity.LEFT; 233 lp.setTitle("RecentsPanel"); 234 lp.windowAnimations = R.style.Animation_RecentPanel; 235 236 WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); 237 mRecentsPanel.setBar(this); 238 } 239 } 240 241 @Override 242 public void start() { 243 super.start(); // will add the main bar view 244 } 245 246 protected View makeStatusBarView() { 247 final Context context = mContext; 248 final Resources res = context.getResources(); 249 250 mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); 251 252 final TabletStatusBarView sb = (TabletStatusBarView)View.inflate( 253 context, R.layout.status_bar, null); 254 mStatusBarView = sb; 255 256 sb.setHandler(mHandler); 257 258 mBarContents = sb.findViewById(R.id.bar_contents); 259 260 // "shadows" of the status bar features, for lights-out mode 261 mBackShadow = sb.findViewById(R.id.back_shadow); 262 mHomeShadow = sb.findViewById(R.id.home_shadow); 263 mRecentShadow = sb.findViewById(R.id.recent_shadow); 264 mMenuShadow = sb.findViewById(R.id.menu_shadow); 265 mNotificationShadow = sb.findViewById(R.id.notification_shadow); 266 267 mShadowController = new ShadowController(false); 268 269 mBackShadow.setOnTouchListener(mShadowController.makeTouchListener()); 270 mHomeShadow.setOnTouchListener(mShadowController.makeTouchListener()); 271 mRecentShadow.setOnTouchListener(mShadowController.makeTouchListener()); 272 mMenuShadow.setOnTouchListener(mShadowController.makeTouchListener()); 273 mNotificationShadow.setOnTouchListener(mShadowController.makeTouchListener()); 274 275 // the whole right-hand side of the bar 276 mNotificationArea = sb.findViewById(R.id.notificationArea); 277 278 // the button to open the notification area 279 mNotificationTrigger = sb.findViewById(R.id.notificationTrigger); 280 mNotificationTrigger.setOnClickListener(mOnClickListener); 281 282 // the more notifications icon 283 mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons); 284 285 // where the icons go 286 mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons); 287 mIconLayout.setOnTouchListener(new NotificationIconTouchListener()); 288 289 ViewConfiguration vc = ViewConfiguration.get(context); 290 mNotificationPeekTapDuration = vc.getTapTimeout(); 291 mNotificationFlingVelocity = 300; // px/s 292 293 mTicker = new TabletTicker(context, (FrameLayout)sb.findViewById(R.id.ticker)); 294 295 // The icons 296 mBatteryController = new BatteryController(mContext); 297 mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); 298 mNetworkController = new NetworkController(mContext); 299 mNetworkController.addCombinedDataIconView((ImageView)sb.findViewById(R.id.network)); 300 301 // The navigation buttons 302 mNavigationArea = sb.findViewById(R.id.navigationArea); 303 mBackButton = mNavigationArea.findViewById(R.id.back); 304 mHomeButton = mNavigationArea.findViewById(R.id.home); 305 mMenuButton = mNavigationArea.findViewById(R.id.menu); 306 mRecentButton = mNavigationArea.findViewById(R.id.recent_apps); 307 Slog.d(TAG, "rec=" + mRecentButton + ", listener=" + mOnClickListener); 308 mRecentButton.setOnClickListener(mOnClickListener); 309 310 // The bar contents buttons 311 mInputMethodButton = (InputMethodButton) sb.findViewById(R.id.imeButton); 312 313 // set the initial view visibility 314 setAreThereNotifications(); 315 refreshNotificationTrigger(); 316 317 // Add the windows 318 addPanelWindows(); 319 320 mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content); 321 mPile.removeAllViews(); 322 323 ScrollView scroller = (ScrollView)mPile.getParent(); 324 scroller.setFillViewport(true); 325 326 return sb; 327 } 328 329 protected int getStatusBarGravity() { 330 return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL; 331 } 332 333 private class H extends Handler { 334 public void handleMessage(Message m) { 335 switch (m.what) { 336 case MSG_OPEN_NOTIFICATION_PEEK: 337 if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1); 338 if (m.arg1 >= 0) { 339 final int N = mNotns.size(); 340 if (mNotificationPeekIndex < N) { 341 NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex); 342 entry.icon.setBackgroundColor(0); 343 } 344 345 final int peekIndex = m.arg1; 346 if (peekIndex < N) { 347 Slog.d(TAG, "loading peek: " + peekIndex); 348 NotificationData.Entry entry = mNotns.get(N-1-peekIndex); 349 NotificationData.Entry copy = new NotificationData.Entry( 350 entry.key, 351 entry.notification, 352 entry.icon); 353 inflateViews(copy, mNotificationPeekRow); 354 355 entry.icon.setBackgroundColor(0x20FFFFFF); 356 357// mNotificationPeekRow.setLayoutTransition( 358// peekIndex < mNotificationPeekIndex 359// ? mNotificationPeekScrubLeft 360// : mNotificationPeekScrubRight); 361 362 mNotificationPeekRow.removeAllViews(); 363 mNotificationPeekRow.addView(copy.row); 364 365 mNotificationPeekWindow.setVisibility(View.VISIBLE); 366 mNotificationPanel.setVisibility(View.GONE); 367 368 mNotificationPeekIndex = peekIndex; 369 } 370 } 371 break; 372 case MSG_CLOSE_NOTIFICATION_PEEK: 373 if (DEBUG) Slog.d(TAG, "closing notification peek window"); 374 mNotificationPeekWindow.setVisibility(View.GONE); 375 mNotificationPeekRow.removeAllViews(); 376 final int N = mNotns.size(); 377 if (mNotificationPeekIndex < N) { 378 NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex); 379 entry.icon.setBackgroundColor(0); 380 } 381 break; 382 case MSG_OPEN_NOTIFICATION_PANEL: 383 if (DEBUG) Slog.d(TAG, "opening notifications panel"); 384 if (mNotificationPanel.getVisibility() == View.GONE) { 385 mNotificationPeekWindow.setVisibility(View.GONE); 386 387 mNotificationPanel.setVisibility(View.VISIBLE); 388 389 // XXX: need to synchronize with shadows here 390 mNotificationArea.setVisibility(View.GONE); 391 } 392 break; 393 case MSG_CLOSE_NOTIFICATION_PANEL: 394 if (DEBUG) Slog.d(TAG, "closing notifications panel"); 395 if (mNotificationPanel.getVisibility() == View.VISIBLE) { 396 mNotificationPanel.setVisibility(View.GONE); 397 398 // XXX: need to synchronize with shadows here 399 mNotificationArea.setVisibility(View.VISIBLE); 400 } 401 break; 402 case MSG_OPEN_RECENTS_PANEL: 403 if (DEBUG) Slog.d(TAG, "opening recents panel"); 404 if (mRecentsPanel != null) mRecentsPanel.setVisibility(View.VISIBLE); 405 break; 406 case MSG_CLOSE_RECENTS_PANEL: 407 if (DEBUG) Slog.d(TAG, "closing recents panel"); 408 if (mRecentsPanel != null) mRecentsPanel.setVisibility(View.GONE); 409 break; 410 case MSG_HIDE_SHADOWS: 411 if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)"); 412 mShadowController.hideAllShadows(); 413 break; 414 case MSG_SHOW_SHADOWS: 415 if (DEBUG) Slog.d(TAG, "showing shadows (lights out)"); 416 animateCollapse(); 417 mShadowController.showAllShadows(); 418 break; 419 case MSG_RESTORE_SHADOWS: 420 if (DEBUG) Slog.d(TAG, "quickly re-showing shadows if appropriate"); 421 mShadowController.refresh(); 422 break; 423 } 424 } 425 } 426 427 public void refreshNotificationTrigger() { 428 /* 429 if (mNotificationTrigger == null) return; 430 431 int resId; 432 boolean panel = (mNotificationPanel != null 433 && mNotificationPanel.getVisibility() == View.VISIBLE); 434 if (!mNotificationsOn) { 435 resId = R.drawable.ic_sysbar_noti_dnd; 436 } else if (mNotns.size() > 0) { 437 resId = panel ? R.drawable.ic_sysbar_noti_avail_open : R.drawable.ic_sysbar_noti_avail; 438 } else { 439 resId = panel ? R.drawable.ic_sysbar_noti_none_open : R.drawable.ic_sysbar_noti_none; 440 } 441 //mNotificationTrigger.setImageResource(resId); 442 */ 443 } 444 445 public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 446 if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon); 447 } 448 449 public void updateIcon(String slot, int index, int viewIndex, 450 StatusBarIcon old, StatusBarIcon icon) { 451 if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon); 452 } 453 454 public void removeIcon(String slot, int index, int viewIndex) { 455 if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")"); 456 } 457 458 public void addNotification(IBinder key, StatusBarNotification notification) { 459 if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); 460 addNotificationViews(key, notification); 461 462 boolean immersive = false; 463 try { 464 immersive = ActivityManagerNative.getDefault().isTopActivityImmersive(); 465 Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 466 } catch (RemoteException ex) { 467 } 468 if (false && immersive) { 469 // TODO: immersive mode popups for tablet 470 } else if (notification.notification.fullScreenIntent != null) { 471 // not immersive & a full-screen alert should be shown 472 Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;" 473 + " sending fullScreenIntent"); 474 try { 475 notification.notification.fullScreenIntent.send(); 476 } catch (PendingIntent.CanceledException e) { 477 } 478 } else { 479 tick(notification); 480 } 481 482 setAreThereNotifications(); 483 } 484 485 public void updateNotification(IBinder key, StatusBarNotification notification) { 486 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ") // TODO"); 487 488 final NotificationData.Entry oldEntry = mNotns.findByKey(key); 489 if (oldEntry == null) { 490 Slog.w(TAG, "updateNotification for unknown key: " + key); 491 return; 492 } 493 494 final StatusBarNotification oldNotification = oldEntry.notification; 495 final RemoteViews oldContentView = oldNotification.notification.contentView; 496 497 final RemoteViews contentView = notification.notification.contentView; 498 499 if (false) { 500 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 501 + " ongoing=" + oldNotification.isOngoing() 502 + " expanded=" + oldEntry.expanded 503 + " contentView=" + oldContentView); 504 Slog.d(TAG, "new notification: when=" + notification.notification.when 505 + " ongoing=" + oldNotification.isOngoing() 506 + " contentView=" + contentView); 507 } 508 509 // Can we just reapply the RemoteViews in place? If when didn't change, the order 510 // didn't change. 511 if (notification.notification.when == oldNotification.notification.when 512 && notification.isOngoing() == oldNotification.isOngoing() 513 && oldEntry.expanded != null 514 && contentView != null 515 && oldContentView != null 516 && contentView.getPackage() != null 517 && oldContentView.getPackage() != null 518 && oldContentView.getPackage().equals(contentView.getPackage()) 519 && oldContentView.getLayoutId() == contentView.getLayoutId()) { 520 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 521 oldEntry.notification = notification; 522 try { 523 // Reapply the RemoteViews 524 contentView.reapply(mContext, oldEntry.content); 525 // update the contentIntent 526 final PendingIntent contentIntent = notification.notification.contentIntent; 527 if (contentIntent != null) { 528 oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent, 529 notification.pkg, notification.tag, notification.id)); 530 } else { 531 oldEntry.content.setOnClickListener(null); 532 } 533 // Update the icon. 534 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 535 notification.notification.icon, notification.notification.iconLevel, 536 notification.notification.number); 537 if (!oldEntry.icon.set(ic)) { 538 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 539 return; 540 } 541 } 542 catch (RuntimeException e) { 543 // It failed to add cleanly. Log, and remove the view from the panel. 544 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 545 removeNotificationViews(key); 546 addNotificationViews(key, notification); 547 } 548 } else { 549 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 550 removeNotificationViews(key); 551 addNotificationViews(key, notification); 552 } 553 // TODO: ticker; immersive mode 554 555 setAreThereNotifications(); 556 } 557 558 public void removeNotification(IBinder key) { 559 if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO"); 560 removeNotificationViews(key); 561 setAreThereNotifications(); 562 } 563 564 public void disable(int state) { 565 int old = mDisabled; 566 int diff = state ^ old; 567 mDisabled = state; 568 569 // act accordingly 570 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 571 if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { 572 Slog.d(TAG, "DISABLE_EXPAND: yes"); 573 animateCollapse(); 574 } 575 } 576 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 577 if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 578 Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); 579 mNotificationIconArea.setVisibility(View.GONE); 580 mTicker.halt(); 581 } else { 582 Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); 583 mNotificationIconArea.setVisibility(View.VISIBLE); 584 } 585 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 586 if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 587 mTicker.halt(); 588 } 589 } 590 if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) { 591 if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) { 592 Slog.d(TAG, "DISABLE_NAVIGATION: yes"); 593 mNavigationArea.setVisibility(View.GONE); 594 } else { 595 Slog.d(TAG, "DISABLE_NAVIGATION: no"); 596 mNavigationArea.setVisibility(View.VISIBLE); 597 } 598 } 599 } 600 601 private boolean hasTicker(Notification n) { 602 return !TextUtils.isEmpty(n.tickerText) 603 || !TextUtils.isEmpty(n.tickerTitle) 604 || !TextUtils.isEmpty(n.tickerSubtitle); 605 } 606 607 private void tick(StatusBarNotification n) { 608 // Don't show the ticker when the windowshade is open. 609 if (mNotificationPanel.getVisibility() == View.VISIBLE) { 610 return; 611 } 612 // Show the ticker if one is requested. Also don't do this 613 // until status bar window is attached to the window manager, 614 // because... well, what's the point otherwise? And trying to 615 // run a ticker without being attached will crash! 616 if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) { 617 if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS 618 | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { 619 mTicker.add(n); 620 } 621 } 622 } 623 624 public void animateExpand() { 625 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 626 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 627 } 628 629 public void animateCollapse() { 630 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); 631 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); 632 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 633 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 634 } 635 636 // called by StatusBar 637 @Override 638 public void setLightsOn(boolean on) { 639 mHandler.removeMessages(MSG_SHOW_SHADOWS); 640 mHandler.removeMessages(MSG_HIDE_SHADOWS); 641 mHandler.sendEmptyMessage(on ? MSG_HIDE_SHADOWS : MSG_SHOW_SHADOWS); 642 } 643 644 public void setMenuKeyVisible(boolean visible) { 645 if (DEBUG) { 646 Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button"); 647 } 648 mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE); 649 } 650 651 public void setIMEButtonVisible(boolean visible) { 652 if (DEBUG) { 653 Slog.d(TAG, (visible?"showing":"hiding") + " the IME button"); 654 } 655 mInputMethodButton.setIMEButtonVisible(visible); 656 } 657 658 private void setAreThereNotifications() { 659 final boolean hasClearable = mNotns.hasClearableItems(); 660 661 //Slog.d(TAG, "setAreThereNotifications hasClerable=" + hasClearable); 662 663 /* 664 mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE); 665 mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE); 666 667 if (ongoing || latest) { 668 mNoNotificationsTitle.setVisibility(View.GONE); 669 } else { 670 mNoNotificationsTitle.setVisibility(View.VISIBLE); 671 } 672 */ 673 } 674 675 /** 676 * Cancel this notification and tell the status bar service about the failure. Hold no locks. 677 */ 678 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 679 removeNotification(key); 680 try { 681 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 682 } catch (RemoteException ex) { 683 // The end is nigh. 684 } 685 } 686 687 private View.OnClickListener mOnClickListener = new View.OnClickListener() { 688 public void onClick(View v) { 689 if (v == mNotificationTrigger) { 690 onClickNotificationTrigger(); 691 } else if (v == mRecentButton) { 692 onClickRecentButton(); 693 } 694 } 695 }; 696 697 public void onClickNotificationTrigger() { 698 if (DEBUG) Slog.d(TAG, "clicked notification icons; disabled=" + mDisabled); 699 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 700 if (!mNotificationsOn) { 701 mNotificationsOn = true; 702 mIconLayout.setVisibility(View.VISIBLE); // TODO: animation 703 refreshNotificationTrigger(); 704 } else { 705 int msg = (mNotificationPanel.getVisibility() == View.GONE) 706 ? MSG_OPEN_NOTIFICATION_PANEL 707 : MSG_CLOSE_NOTIFICATION_PANEL; 708 mHandler.removeMessages(msg); 709 mHandler.sendEmptyMessage(msg); 710 } 711 } 712 } 713 714 public void onClickRecentButton() { 715 if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); 716 if (mRecentsPanel == null) { 717 Intent intent = new Intent(); 718 intent.setClass(mContext, RecentApplicationsActivity.class); 719 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 720 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 721 mContext.startActivity(intent); 722 } else { 723 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 724 int msg = (mRecentsPanel.getVisibility() == View.GONE) 725 ? MSG_OPEN_RECENTS_PANEL 726 : MSG_CLOSE_RECENTS_PANEL; 727 mHandler.removeMessages(msg); 728 mHandler.sendEmptyMessage(msg); 729 } 730 } 731 } 732 733 private class NotificationClicker implements View.OnClickListener { 734 private PendingIntent mIntent; 735 private String mPkg; 736 private String mTag; 737 private int mId; 738 739 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 740 mIntent = intent; 741 mPkg = pkg; 742 mTag = tag; 743 mId = id; 744 } 745 746 public void onClick(View v) { 747 try { 748 // The intent we are sending is for the application, which 749 // won't have permission to immediately start an activity after 750 // the user switches to home. We know it is safe to do at this 751 // point, so make sure new activity switches are now allowed. 752 ActivityManagerNative.getDefault().resumeAppSwitches(); 753 } catch (RemoteException e) { 754 } 755 756 if (mIntent != null) { 757 int[] pos = new int[2]; 758 v.getLocationOnScreen(pos); 759 Intent overlay = new Intent(); 760 overlay.setSourceBounds( 761 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 762 try { 763 mIntent.send(mContext, 0, overlay); 764 } catch (PendingIntent.CanceledException e) { 765 // the stack trace isn't very helpful here. Just log the exception message. 766 Slog.w(TAG, "Sending contentIntent failed: " + e); 767 } 768 } 769 770 try { 771 mBarService.onNotificationClick(mPkg, mTag, mId); 772 } catch (RemoteException ex) { 773 // system process is dead if we're here. 774 } 775 776 // close the shade if it was open 777 animateCollapse(); 778 779 // If this click was on the intruder alert, hide that instead 780// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 781 } 782 } 783 784 StatusBarNotification removeNotificationViews(IBinder key) { 785 NotificationData.Entry entry = mNotns.remove(key); 786 if (entry == null) { 787 Slog.w(TAG, "removeNotification for unknown key: " + key); 788 return null; 789 } 790 // Remove the expanded view. 791 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 792 if (rowParent != null) rowParent.removeView(entry.row); 793 // Remove the icon. 794// ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); 795// if (iconParent != null) iconParent.removeView(entry.icon); 796 refreshIcons(); 797 798 return entry.notification; 799 } 800 801 private class NotificationIconTouchListener implements View.OnTouchListener { 802 VelocityTracker mVT; 803 804 public NotificationIconTouchListener() { 805 } 806 807 public boolean onTouch(View v, MotionEvent event) { 808 boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; 809 boolean panelShowing = mNotificationPanel.getVisibility() != View.GONE; 810 if (panelShowing) return false; 811 812 switch (event.getAction()) { 813 case MotionEvent.ACTION_DOWN: 814 mVT = VelocityTracker.obtain(); 815 816 // fall through 817 case MotionEvent.ACTION_OUTSIDE: 818 case MotionEvent.ACTION_MOVE: 819 // peek and switch icons if necessary 820 int numIcons = mIconLayout.getChildCount(); 821 int peekIndex = 822 (int)((float)event.getX() * numIcons / mIconLayout.getWidth()); 823 if (peekIndex > numIcons - 1) peekIndex = numIcons - 1; 824 else if (peekIndex < 0) peekIndex = 0; 825 826 if (!peeking || mNotificationPeekIndex != peekIndex) { 827 if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex); 828 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 829 peekMsg.arg1 = peekIndex; 830 831 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 832 833 // no delay if we're scrubbing left-right 834 mHandler.sendMessageDelayed(peekMsg, 835 peeking ? 0 : mNotificationPeekTapDuration); 836 } 837 838 // check for fling 839 if (mVT != null) { 840 mVT.addMovement(event); 841 mVT.computeCurrentVelocity(1000); 842 // require a little more oomph once we're already in peekaboo mode 843 if (!panelShowing && ( 844 (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) 845 || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { 846 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 847 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 848 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 849 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 850 } 851 } 852 return true; 853 case MotionEvent.ACTION_UP: 854 case MotionEvent.ACTION_CANCEL: 855 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 856 if (peeking) { 857 mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 250); 858 } 859 mVT.recycle(); 860 mVT = null; 861 return true; 862 } 863 return false; 864 } 865 } 866 867 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 868 if (DEBUG) { 869 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 870 } 871 // Construct the icon. 872 final StatusBarIconView iconView = new StatusBarIconView(mContext, 873 notification.pkg + "/0x" + Integer.toHexString(notification.id)); 874 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 875 876 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 877 notification.notification.icon, 878 notification.notification.iconLevel, 879 notification.notification.number); 880 if (!iconView.set(ic)) { 881 handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); 882 return null; 883 } 884 // Construct the expanded view. 885 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 886 if (!inflateViews(entry, mPile)) { 887 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 888 + notification); 889 return null; 890 } 891 892 // Add the icon. 893 mNotns.add(entry); 894 refreshIcons(); 895 896 return iconView; 897 } 898 899 private void refreshIcons() { 900 // XXX: need to implement a new limited linear layout class 901 // to avoid removing & readding everything 902 903 final int ICON_LIMIT = 4; 904 final LinearLayout.LayoutParams params 905 = new LinearLayout.LayoutParams(mIconSize, mIconSize); 906 907 int N = mNotns.size(); 908 909 if (DEBUG) { 910 Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); 911 } 912 913 ArrayList<View> toShow = new ArrayList<View>(); 914 915 for (int i=0; i<ICON_LIMIT; i++) { 916 if (i>=N) break; 917 toShow.add(mNotns.get(N-i-1).icon); 918 } 919 920 ArrayList<View> toRemove = new ArrayList<View>(); 921 for (int i=0; i<mIconLayout.getChildCount(); i++) { 922 View child = mIconLayout.getChildAt(i); 923 if (!toShow.contains(child)) { 924 toRemove.add(child); 925 } 926 } 927 928 for (View remove : toRemove) { 929 mIconLayout.removeView(remove); 930 } 931 932 for (int i=0; i<toShow.size(); i++) { 933 View v = toShow.get(i); 934 if (v.getParent() == null) { 935 mIconLayout.addView(toShow.get(i), i, params); 936 } 937 } 938 939 loadNotificationPanel(); 940 refreshNotificationTrigger(); 941 } 942 943 private void loadNotificationPanel() { 944 int N = mNotns.size(); 945 946 ArrayList<View> toShow = new ArrayList<View>(); 947 948 for (int i=0; i<N; i++) { 949 View row = mNotns.get(N-i-1).row; 950 toShow.add(row); 951 } 952 953 ArrayList<View> toRemove = new ArrayList<View>(); 954 for (int i=0; i<mPile.getChildCount(); i++) { 955 View child = mPile.getChildAt(i); 956 if (!toShow.contains(child)) { 957 toRemove.add(child); 958 } 959 } 960 961 for (View remove : toRemove) { 962 mPile.removeView(remove); 963 } 964 965 for (int i=0; i<toShow.size(); i++) { 966 View v = toShow.get(i); 967 if (v.getParent() == null) { 968 mPile.addView(toShow.get(i)); 969 } 970 } 971 } 972 973 void workAroundBadLayerDrawableOpacity(View v) { 974 LayerDrawable d = (LayerDrawable)v.getBackground(); 975 v.setBackgroundDrawable(null); 976 d.setOpacity(PixelFormat.TRANSLUCENT); 977 v.setBackgroundDrawable(d); 978 } 979 980 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 981 StatusBarNotification sbn = entry.notification; 982 RemoteViews remoteViews = sbn.notification.contentView; 983 if (remoteViews == null) { 984 return false; 985 } 986 987 // create the row view 988 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 989 Context.LAYOUT_INFLATER_SERVICE); 990 View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false); 991 workAroundBadLayerDrawableOpacity(row); 992 View vetoButton = row.findViewById(R.id.veto); 993 if (entry.notification.isClearable()) { 994 final String _pkg = sbn.pkg; 995 final String _tag = sbn.tag; 996 final int _id = sbn.id; 997 vetoButton.setOnClickListener(new View.OnClickListener() { 998 public void onClick(View v) { 999 try { 1000 mBarService.onNotificationClear(_pkg, _tag, _id); 1001 } catch (RemoteException ex) { 1002 // system process is dead if we're here. 1003 } 1004 // animateCollapse(); 1005 } 1006 }); 1007 } else { 1008 vetoButton.setVisibility(View.INVISIBLE); 1009 } 1010 1011 // bind the click event to the content area 1012 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 1013 // XXX: update to allow controls within notification views 1014 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1015// content.setOnFocusChangeListener(mFocusChangeListener); 1016 PendingIntent contentIntent = sbn.notification.contentIntent; 1017 if (contentIntent != null) { 1018 content.setOnClickListener(new NotificationClicker(contentIntent, 1019 sbn.pkg, sbn.tag, sbn.id)); 1020 } else { 1021 content.setOnClickListener(null); 1022 } 1023 1024 View expanded = null; 1025 Exception exception = null; 1026 try { 1027 expanded = remoteViews.apply(mContext, content); 1028 } 1029 catch (RuntimeException e) { 1030 exception = e; 1031 } 1032 if (expanded == null) { 1033 String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 1034 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 1035 return false; 1036 } else { 1037 content.addView(expanded); 1038 row.setDrawingCacheEnabled(true); 1039 } 1040 1041 entry.row = row; 1042 entry.content = content; 1043 entry.expanded = expanded; 1044 1045 return true; 1046 } 1047 1048 public class ShadowController { 1049 boolean mShowShadows; 1050 View mTouchTarget; 1051 1052 ShadowController(boolean showShadows) { 1053 mShowShadows = showShadows; 1054 mTouchTarget = null; 1055 } 1056 1057 public boolean getShadowState() { 1058 return mShowShadows; 1059 } 1060 1061 public View.OnTouchListener makeTouchListener() { 1062 return new View.OnTouchListener() { 1063 public boolean onTouch(View v, MotionEvent ev) { 1064 final int action = ev.getAction(); 1065 1066 if (DEBUG) Slog.d(TAG, "ShadowController: v=" + v + ", ev=" + ev); 1067 1068 // currently redirecting events? 1069 if (mTouchTarget == null) { 1070 if (v == mBackShadow) { 1071 mTouchTarget = mBackButton; 1072 } else if (v == mHomeShadow) { 1073 mTouchTarget = mHomeButton; 1074 } else if (v == mMenuShadow) { 1075 mTouchTarget = mMenuButton; 1076 } else if (v == mRecentShadow) { 1077 mTouchTarget = mRecentButton; 1078 } else if (v == mNotificationShadow) { 1079 mTouchTarget = mNotificationArea; 1080 } 1081 } 1082 1083 if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) { 1084 boolean last = false; 1085 switch (action) { 1086 case MotionEvent.ACTION_CANCEL: 1087 case MotionEvent.ACTION_UP: 1088 mHandler.removeMessages(MSG_RESTORE_SHADOWS); 1089 if (mShowShadows) { 1090 mHandler.sendEmptyMessageDelayed(MSG_RESTORE_SHADOWS, 1091 v == mNotificationShadow ? 5000 : 500); 1092 } 1093 last = true; 1094 break; 1095 case MotionEvent.ACTION_DOWN: 1096 mHandler.removeMessages(MSG_RESTORE_SHADOWS); 1097 setShadowForButton(mTouchTarget, false); 1098 break; 1099 } 1100 mTouchTarget.dispatchTouchEvent(ev); 1101 if (last) mTouchTarget = null; 1102 return true; 1103 } 1104 1105 return false; 1106 } 1107 }; 1108 } 1109 1110 public void refresh() { 1111 setShadowForButton(mBackButton, mShowShadows); 1112 setShadowForButton(mHomeButton, mShowShadows); 1113 setShadowForButton(mRecentButton, mShowShadows); 1114 setShadowForButton(mMenuButton, mShowShadows); 1115 setShadowForButton(mNotificationArea, mShowShadows); 1116 } 1117 1118 public void showAllShadows() { 1119 mShowShadows = true; 1120 refresh(); 1121 } 1122 1123 public void hideAllShadows() { 1124 mShowShadows = false; 1125 refresh(); 1126 } 1127 1128 // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are 1129 // disabled (and should not be shadowed or re-shown) 1130 public void setShadowForButton(View button, boolean shade) { 1131 View shadow = null; 1132 if (button == mBackButton) { 1133 shadow = mBackShadow; 1134 } else if (button == mHomeButton) { 1135 shadow = mHomeShadow; 1136 } else if (button == mMenuButton) { 1137 shadow = mMenuShadow; 1138 } else if (button == mRecentButton) { 1139 shadow = mRecentShadow; 1140 } else if (button == mNotificationArea) { 1141 shadow = mNotificationShadow; 1142 } 1143 if (shadow != null) { 1144 if (button.getVisibility() != View.GONE) { 1145 shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE); 1146 button.setVisibility(shade ? View.INVISIBLE : View.VISIBLE); 1147 } 1148 } 1149 } 1150 } 1151 1152 public class TouchOutsideListener implements View.OnTouchListener { 1153 private int mMsg; 1154 private StatusBarPanel mPanel; 1155 1156 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1157 mMsg = msg; 1158 mPanel = panel; 1159 } 1160 1161 public boolean onTouch(View v, MotionEvent ev) { 1162 final int action = ev.getAction(); 1163 if (action == MotionEvent.ACTION_OUTSIDE 1164 || (action == MotionEvent.ACTION_DOWN 1165 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1166 mHandler.removeMessages(mMsg); 1167 mHandler.sendEmptyMessage(mMsg); 1168 return true; 1169 } 1170 return false; 1171 } 1172 } 1173 1174 private void setViewVisibility(View v, int vis, int anim) { 1175 if (v.getVisibility() != vis) { 1176 //Slog.d(TAG, "setViewVisibility vis=" + (vis == View.VISIBLE) + " v=" + v); 1177 v.setAnimation(AnimationUtils.loadAnimation(mContext, anim)); 1178 v.setVisibility(vis); 1179 } 1180 } 1181 1182 1183 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1184 pw.print("mDisabled=0x"); 1185 pw.println(Integer.toHexString(mDisabled)); 1186 } 1187} 1188 1189 1190