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