TabletStatusBar.java revision b6d3dc68977eada24515a825c43eb01df9a4edb5
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_SHOW_SHADOWS_NO_COLLAPSE = 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 mShadowController.hideAllShadows(); 410 break; 411 case MSG_SHOW_SHADOWS: 412 animateCollapse(); 413 // fall through 414 case MSG_SHOW_SHADOWS_NO_COLLAPSE: 415 mShadowController.showAllShadows(); 416 break; 417 } 418 } 419 } 420 421 public void refreshNotificationTrigger() { 422 /* 423 if (mNotificationTrigger == null) return; 424 425 int resId; 426 boolean panel = (mNotificationPanel != null 427 && mNotificationPanel.getVisibility() == View.VISIBLE); 428 if (!mNotificationsOn) { 429 resId = R.drawable.ic_sysbar_noti_dnd; 430 } else if (mNotns.size() > 0) { 431 resId = panel ? R.drawable.ic_sysbar_noti_avail_open : R.drawable.ic_sysbar_noti_avail; 432 } else { 433 resId = panel ? R.drawable.ic_sysbar_noti_none_open : R.drawable.ic_sysbar_noti_none; 434 } 435 //mNotificationTrigger.setImageResource(resId); 436 */ 437 } 438 439 public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 440 if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon); 441 } 442 443 public void updateIcon(String slot, int index, int viewIndex, 444 StatusBarIcon old, StatusBarIcon icon) { 445 if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon); 446 } 447 448 public void removeIcon(String slot, int index, int viewIndex) { 449 if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")"); 450 } 451 452 public void addNotification(IBinder key, StatusBarNotification notification) { 453 if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); 454 addNotificationViews(key, notification); 455 456 boolean immersive = false; 457 try { 458 immersive = ActivityManagerNative.getDefault().isTopActivityImmersive(); 459 Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 460 } catch (RemoteException ex) { 461 } 462 if (false && immersive) { 463 // TODO: immersive mode popups for tablet 464 } else if (notification.notification.fullScreenIntent != null) { 465 // not immersive & a full-screen alert should be shown 466 Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;" 467 + " sending fullScreenIntent"); 468 try { 469 notification.notification.fullScreenIntent.send(); 470 } catch (PendingIntent.CanceledException e) { 471 } 472 } else { 473 tick(notification); 474 } 475 476 setAreThereNotifications(); 477 } 478 479 public void updateNotification(IBinder key, StatusBarNotification notification) { 480 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ") // TODO"); 481 482 final NotificationData.Entry oldEntry = mNotns.findByKey(key); 483 if (oldEntry == null) { 484 Slog.w(TAG, "updateNotification for unknown key: " + key); 485 return; 486 } 487 488 final StatusBarNotification oldNotification = oldEntry.notification; 489 final RemoteViews oldContentView = oldNotification.notification.contentView; 490 491 final RemoteViews contentView = notification.notification.contentView; 492 493 if (false) { 494 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 495 + " ongoing=" + oldNotification.isOngoing() 496 + " expanded=" + oldEntry.expanded 497 + " contentView=" + oldContentView); 498 Slog.d(TAG, "new notification: when=" + notification.notification.when 499 + " ongoing=" + oldNotification.isOngoing() 500 + " contentView=" + contentView); 501 } 502 503 // Can we just reapply the RemoteViews in place? If when didn't change, the order 504 // didn't change. 505 if (notification.notification.when == oldNotification.notification.when 506 && notification.isOngoing() == oldNotification.isOngoing() 507 && oldEntry.expanded != null 508 && contentView != null 509 && oldContentView != null 510 && contentView.getPackage() != null 511 && oldContentView.getPackage() != null 512 && oldContentView.getPackage().equals(contentView.getPackage()) 513 && oldContentView.getLayoutId() == contentView.getLayoutId()) { 514 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 515 oldEntry.notification = notification; 516 try { 517 // Reapply the RemoteViews 518 contentView.reapply(mContext, oldEntry.content); 519 // update the contentIntent 520 final PendingIntent contentIntent = notification.notification.contentIntent; 521 if (contentIntent != null) { 522 oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent, 523 notification.pkg, notification.tag, notification.id)); 524 } else { 525 oldEntry.content.setOnClickListener(null); 526 } 527 // Update the icon. 528 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 529 notification.notification.icon, notification.notification.iconLevel, 530 notification.notification.number); 531 if (!oldEntry.icon.set(ic)) { 532 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 533 return; 534 } 535 } 536 catch (RuntimeException e) { 537 // It failed to add cleanly. Log, and remove the view from the panel. 538 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 539 removeNotificationViews(key); 540 addNotificationViews(key, notification); 541 } 542 } else { 543 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 544 removeNotificationViews(key); 545 addNotificationViews(key, notification); 546 } 547 // TODO: ticker; immersive mode 548 549 setAreThereNotifications(); 550 } 551 552 public void removeNotification(IBinder key) { 553 if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO"); 554 removeNotificationViews(key); 555 setAreThereNotifications(); 556 } 557 558 public void disable(int state) { 559 int old = mDisabled; 560 int diff = state ^ old; 561 mDisabled = state; 562 563 // act accordingly 564 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 565 if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { 566 Slog.d(TAG, "DISABLE_EXPAND: yes"); 567 animateCollapse(); 568 } 569 } 570 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 571 if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 572 Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); 573 mNotificationIconArea.setVisibility(View.GONE); 574 mTicker.halt(); 575 } else { 576 Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); 577 mNotificationIconArea.setVisibility(View.VISIBLE); 578 } 579 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 580 if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 581 mTicker.halt(); 582 } 583 } 584 if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) { 585 if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) { 586 Slog.d(TAG, "DISABLE_NAVIGATION: yes"); 587 mNavigationArea.setVisibility(View.GONE); 588 } else { 589 Slog.d(TAG, "DISABLE_NAVIGATION: no"); 590 mNavigationArea.setVisibility(View.VISIBLE); 591 } 592 } 593 } 594 595 private boolean hasTicker(Notification n) { 596 return !TextUtils.isEmpty(n.tickerText) 597 || !TextUtils.isEmpty(n.tickerTitle) 598 || !TextUtils.isEmpty(n.tickerSubtitle); 599 } 600 601 private void tick(StatusBarNotification n) { 602 // Don't show the ticker when the windowshade is open. 603 if (mNotificationPanel.getVisibility() == View.VISIBLE) { 604 return; 605 } 606 // Show the ticker if one is requested. Also don't do this 607 // until status bar window is attached to the window manager, 608 // because... well, what's the point otherwise? And trying to 609 // run a ticker without being attached will crash! 610 if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) { 611 if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS 612 | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { 613 mTicker.add(n); 614 } 615 } 616 } 617 618 public void animateExpand() { 619 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 620 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 621 } 622 623 public void animateCollapse() { 624 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); 625 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); 626 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 627 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 628 } 629 630 // called by StatusBar 631 @Override 632 public void setLightsOn(boolean on) { 633 mHandler.removeMessages(MSG_SHOW_SHADOWS); 634 mHandler.removeMessages(MSG_HIDE_SHADOWS); 635 mHandler.sendEmptyMessage(on ? MSG_HIDE_SHADOWS : MSG_SHOW_SHADOWS); 636 } 637 638 public void setMenuKeyVisible(boolean visible) { 639 if (DEBUG) { 640 Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button"); 641 } 642 mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE); 643 } 644 645 public void setIMEButtonVisible(boolean visible) { 646 if (DEBUG) { 647 Slog.d(TAG, (visible?"showing":"hiding") + " the IME button"); 648 } 649 mInputMethodButton.setIMEButtonVisible(visible); 650 } 651 652 private void setAreThereNotifications() { 653 final boolean hasClearable = mNotns.hasClearableItems(); 654 655 //Slog.d(TAG, "setAreThereNotifications hasClerable=" + hasClearable); 656 657 /* 658 mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE); 659 mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE); 660 661 if (ongoing || latest) { 662 mNoNotificationsTitle.setVisibility(View.GONE); 663 } else { 664 mNoNotificationsTitle.setVisibility(View.VISIBLE); 665 } 666 */ 667 } 668 669 /** 670 * Cancel this notification and tell the status bar service about the failure. Hold no locks. 671 */ 672 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 673 removeNotification(key); 674 try { 675 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 676 } catch (RemoteException ex) { 677 // The end is nigh. 678 } 679 } 680 681 private View.OnClickListener mOnClickListener = new View.OnClickListener() { 682 public void onClick(View v) { 683 if (v == mNotificationTrigger) { 684 onClickNotificationTrigger(); 685 } else if (v == mRecentButton) { 686 onClickRecentButton(); 687 } 688 } 689 }; 690 691 public void onClickNotificationTrigger() { 692 if (DEBUG) Slog.d(TAG, "clicked notification icons; disabled=" + mDisabled); 693 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 694 if (!mNotificationsOn) { 695 mNotificationsOn = true; 696 mIconLayout.setVisibility(View.VISIBLE); // TODO: animation 697 refreshNotificationTrigger(); 698 } else { 699 int msg = (mNotificationPanel.getVisibility() == View.GONE) 700 ? MSG_OPEN_NOTIFICATION_PANEL 701 : MSG_CLOSE_NOTIFICATION_PANEL; 702 mHandler.removeMessages(msg); 703 mHandler.sendEmptyMessage(msg); 704 } 705 } 706 } 707 708 public void onClickRecentButton() { 709 if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); 710 if (mRecentsPanel == null) { 711 Intent intent = new Intent(); 712 intent.setClass(mContext, RecentApplicationsActivity.class); 713 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 714 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 715 mContext.startActivity(intent); 716 } else { 717 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 718 int msg = (mRecentsPanel.getVisibility() == View.GONE) 719 ? MSG_OPEN_RECENTS_PANEL 720 : MSG_CLOSE_RECENTS_PANEL; 721 mHandler.removeMessages(msg); 722 mHandler.sendEmptyMessage(msg); 723 } 724 } 725 } 726 727 private class NotificationClicker implements View.OnClickListener { 728 private PendingIntent mIntent; 729 private String mPkg; 730 private String mTag; 731 private int mId; 732 733 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 734 mIntent = intent; 735 mPkg = pkg; 736 mTag = tag; 737 mId = id; 738 } 739 740 public void onClick(View v) { 741 try { 742 // The intent we are sending is for the application, which 743 // won't have permission to immediately start an activity after 744 // the user switches to home. We know it is safe to do at this 745 // point, so make sure new activity switches are now allowed. 746 ActivityManagerNative.getDefault().resumeAppSwitches(); 747 } catch (RemoteException e) { 748 } 749 750 if (mIntent != null) { 751 int[] pos = new int[2]; 752 v.getLocationOnScreen(pos); 753 Intent overlay = new Intent(); 754 overlay.setSourceBounds( 755 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 756 try { 757 mIntent.send(mContext, 0, overlay); 758 } catch (PendingIntent.CanceledException e) { 759 // the stack trace isn't very helpful here. Just log the exception message. 760 Slog.w(TAG, "Sending contentIntent failed: " + e); 761 } 762 } 763 764 try { 765 mBarService.onNotificationClick(mPkg, mTag, mId); 766 } catch (RemoteException ex) { 767 // system process is dead if we're here. 768 } 769 770 // close the shade if it was open 771 animateCollapse(); 772 773 // If this click was on the intruder alert, hide that instead 774// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 775 } 776 } 777 778 StatusBarNotification removeNotificationViews(IBinder key) { 779 NotificationData.Entry entry = mNotns.remove(key); 780 if (entry == null) { 781 Slog.w(TAG, "removeNotification for unknown key: " + key); 782 return null; 783 } 784 // Remove the expanded view. 785 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 786 if (rowParent != null) rowParent.removeView(entry.row); 787 // Remove the icon. 788// ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); 789// if (iconParent != null) iconParent.removeView(entry.icon); 790 refreshIcons(); 791 792 return entry.notification; 793 } 794 795 private class NotificationIconTouchListener implements View.OnTouchListener { 796 VelocityTracker mVT; 797 798 public NotificationIconTouchListener() { 799 } 800 801 public boolean onTouch(View v, MotionEvent event) { 802 boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; 803 boolean panelShowing = mNotificationPanel.getVisibility() != View.GONE; 804 if (panelShowing) return false; 805 806 switch (event.getAction()) { 807 case MotionEvent.ACTION_DOWN: 808 mVT = VelocityTracker.obtain(); 809 810 // fall through 811 case MotionEvent.ACTION_OUTSIDE: 812 case MotionEvent.ACTION_MOVE: 813 // peek and switch icons if necessary 814 int numIcons = mIconLayout.getChildCount(); 815 int peekIndex = 816 (int)((float)event.getX() * numIcons / mIconLayout.getWidth()); 817 if (peekIndex > numIcons - 1) peekIndex = numIcons - 1; 818 else if (peekIndex < 0) peekIndex = 0; 819 820 if (!peeking || mNotificationPeekIndex != peekIndex) { 821 if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex); 822 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 823 peekMsg.arg1 = peekIndex; 824 825 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 826 827 // no delay if we're scrubbing left-right 828 mHandler.sendMessageDelayed(peekMsg, 829 peeking ? 0 : mNotificationPeekTapDuration); 830 } 831 832 // check for fling 833 if (mVT != null) { 834 mVT.addMovement(event); 835 mVT.computeCurrentVelocity(1000); 836 // require a little more oomph once we're already in peekaboo mode 837 if (!panelShowing && ( 838 (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) 839 || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { 840 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 841 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 842 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 843 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 844 } 845 } 846 return true; 847 case MotionEvent.ACTION_UP: 848 case MotionEvent.ACTION_CANCEL: 849 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 850 if (peeking) { 851 mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 250); 852 } 853 mVT.recycle(); 854 mVT = null; 855 return true; 856 } 857 return false; 858 } 859 } 860 861 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 862 if (DEBUG) { 863 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 864 } 865 // Construct the icon. 866 final StatusBarIconView iconView = new StatusBarIconView(mContext, 867 notification.pkg + "/0x" + Integer.toHexString(notification.id)); 868 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 869 870 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 871 notification.notification.icon, 872 notification.notification.iconLevel, 873 notification.notification.number); 874 if (!iconView.set(ic)) { 875 handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); 876 return null; 877 } 878 // Construct the expanded view. 879 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 880 if (!inflateViews(entry, mPile)) { 881 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 882 + notification); 883 return null; 884 } 885 886 // Add the icon. 887 mNotns.add(entry); 888 refreshIcons(); 889 890 return iconView; 891 } 892 893 private void refreshIcons() { 894 // XXX: need to implement a new limited linear layout class 895 // to avoid removing & readding everything 896 897 final int ICON_LIMIT = 4; 898 final LinearLayout.LayoutParams params 899 = new LinearLayout.LayoutParams(mIconSize, mIconSize); 900 901 int N = mNotns.size(); 902 903 if (DEBUG) { 904 Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); 905 } 906 907 ArrayList<View> toShow = new ArrayList<View>(); 908 909 for (int i=0; i<ICON_LIMIT; i++) { 910 if (i>=N) break; 911 toShow.add(mNotns.get(N-i-1).icon); 912 } 913 914 ArrayList<View> toRemove = new ArrayList<View>(); 915 for (int i=0; i<mIconLayout.getChildCount(); i++) { 916 View child = mIconLayout.getChildAt(i); 917 if (!toShow.contains(child)) { 918 toRemove.add(child); 919 } 920 } 921 922 for (View remove : toRemove) { 923 mIconLayout.removeView(remove); 924 } 925 926 for (int i=0; i<toShow.size(); i++) { 927 View v = toShow.get(i); 928 if (v.getParent() == null) { 929 mIconLayout.addView(toShow.get(i), i, params); 930 } 931 } 932 933 loadNotificationPanel(); 934 refreshNotificationTrigger(); 935 } 936 937 private void loadNotificationPanel() { 938 int N = mNotns.size(); 939 940 ArrayList<View> toShow = new ArrayList<View>(); 941 942 for (int i=0; i<N; i++) { 943 View row = mNotns.get(N-i-1).row; 944 toShow.add(row); 945 } 946 947 ArrayList<View> toRemove = new ArrayList<View>(); 948 for (int i=0; i<mPile.getChildCount(); i++) { 949 View child = mPile.getChildAt(i); 950 if (!toShow.contains(child)) { 951 toRemove.add(child); 952 } 953 } 954 955 for (View remove : toRemove) { 956 mPile.removeView(remove); 957 } 958 959 for (int i=0; i<toShow.size(); i++) { 960 View v = toShow.get(i); 961 if (v.getParent() == null) { 962 mPile.addView(toShow.get(i)); 963 } 964 } 965 } 966 967 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 968 StatusBarNotification sbn = entry.notification; 969 RemoteViews remoteViews = sbn.notification.contentView; 970 if (remoteViews == null) { 971 return false; 972 } 973 974 // create the row view 975 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 976 Context.LAYOUT_INFLATER_SERVICE); 977 View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false); 978 View vetoButton = row.findViewById(R.id.veto); 979 if (entry.notification.isClearable()) { 980 final String _pkg = sbn.pkg; 981 final String _tag = sbn.tag; 982 final int _id = sbn.id; 983 vetoButton.setOnClickListener(new View.OnClickListener() { 984 public void onClick(View v) { 985 try { 986 mBarService.onNotificationClear(_pkg, _tag, _id); 987 } catch (RemoteException ex) { 988 // system process is dead if we're here. 989 } 990 // animateCollapse(); 991 } 992 }); 993 } else { 994 vetoButton.setVisibility(View.INVISIBLE); 995 } 996 997 // bind the click event to the content area 998 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 999 // XXX: update to allow controls within notification views 1000 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1001// content.setOnFocusChangeListener(mFocusChangeListener); 1002 PendingIntent contentIntent = sbn.notification.contentIntent; 1003 if (contentIntent != null) { 1004 content.setOnClickListener(new NotificationClicker(contentIntent, 1005 sbn.pkg, sbn.tag, sbn.id)); 1006 } else { 1007 content.setOnClickListener(null); 1008 } 1009 1010 View expanded = null; 1011 Exception exception = null; 1012 try { 1013 expanded = remoteViews.apply(mContext, content); 1014 } 1015 catch (RuntimeException e) { 1016 exception = e; 1017 } 1018 if (expanded == null) { 1019 String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 1020 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 1021 return false; 1022 } else { 1023 content.addView(expanded); 1024 row.setDrawingCacheEnabled(true); 1025 } 1026 1027 entry.row = row; 1028 entry.content = content; 1029 entry.expanded = expanded; 1030 1031 return true; 1032 } 1033 1034 public class ShadowController { 1035 boolean mShowShadows; 1036 View mTouchTarget; 1037 1038 ShadowController(boolean showShadows) { 1039 mShowShadows = showShadows; 1040 mTouchTarget = null; 1041 } 1042 1043 public boolean getShadowState() { 1044 return mShowShadows; 1045 } 1046 1047 public View.OnTouchListener makeTouchListener() { 1048 return new View.OnTouchListener() { 1049 public boolean onTouch(View v, MotionEvent ev) { 1050 final int action = ev.getAction(); 1051 1052 if (DEBUG) Slog.d(TAG, "ShadowController: v=" + v + ", ev=" + ev); 1053 1054 // currently redirecting events? 1055 if (mTouchTarget == null) { 1056 if (v == mBackShadow) { 1057 mTouchTarget = mBackButton; 1058 } else if (v == mHomeShadow) { 1059 mTouchTarget = mHomeButton; 1060 } else if (v == mMenuShadow) { 1061 mTouchTarget = mMenuButton; 1062 } else if (v == mRecentShadow) { 1063 mTouchTarget = mRecentButton; 1064 } else if (v == mNotificationShadow) { 1065 mTouchTarget = mNotificationArea; 1066 } 1067 } 1068 1069 if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) { 1070 boolean last = false; 1071 switch (action) { 1072 case MotionEvent.ACTION_CANCEL: 1073 case MotionEvent.ACTION_UP: 1074 mHandler.removeMessages(MSG_SHOW_SHADOWS_NO_COLLAPSE); 1075 if (mShowShadows) { 1076 mHandler.sendEmptyMessageDelayed(MSG_SHOW_SHADOWS_NO_COLLAPSE, 1077 v == mNotificationShadow ? 5000 : 500); 1078 } 1079 last = true; 1080 break; 1081 case MotionEvent.ACTION_DOWN: 1082 mHandler.removeMessages(MSG_SHOW_SHADOWS_NO_COLLAPSE); 1083 setShadowForButton(mTouchTarget, false); 1084 break; 1085 } 1086 mTouchTarget.dispatchTouchEvent(ev); 1087 if (last) mTouchTarget = null; 1088 return true; 1089 } 1090 1091 return false; 1092 } 1093 }; 1094 } 1095 1096 public void showAllShadows() { 1097 mShowShadows = true; 1098 setShadowForButton(mBackButton, true); 1099 setShadowForButton(mHomeButton, true); 1100 setShadowForButton(mRecentButton, true); 1101 setShadowForButton(mMenuButton, true); 1102 setShadowForButton(mNotificationArea, true); 1103 } 1104 1105 public void hideAllShadows() { 1106 mShowShadows = false; 1107 setShadowForButton(mBackButton, false); 1108 setShadowForButton(mHomeButton, false); 1109 setShadowForButton(mRecentButton, false); 1110 setShadowForButton(mMenuButton, false); 1111 setShadowForButton(mNotificationArea, false); 1112 } 1113 1114 // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are 1115 // disabled (and should not be shadowed or re-shown) 1116 public void setShadowForButton(View button, boolean shade) { 1117 View shadow = null; 1118 if (button == mBackButton) { 1119 shadow = mBackShadow; 1120 } else if (button == mHomeButton) { 1121 shadow = mHomeShadow; 1122 } else if (button == mMenuButton) { 1123 shadow = mMenuShadow; 1124 } else if (button == mRecentButton) { 1125 shadow = mRecentShadow; 1126 } else if (button == mNotificationArea) { 1127 shadow = mNotificationShadow; 1128 } 1129 if (shadow != null) { 1130 if (button.getVisibility() != View.GONE) { 1131 shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE); 1132 button.setVisibility(shade ? View.INVISIBLE : View.VISIBLE); 1133 } 1134 } 1135 } 1136 } 1137 1138 public class TouchOutsideListener implements View.OnTouchListener { 1139 private int mMsg; 1140 private StatusBarPanel mPanel; 1141 1142 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1143 mMsg = msg; 1144 mPanel = panel; 1145 } 1146 1147 public boolean onTouch(View v, MotionEvent ev) { 1148 final int action = ev.getAction(); 1149 if (action == MotionEvent.ACTION_OUTSIDE 1150 || (action == MotionEvent.ACTION_DOWN 1151 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1152 mHandler.removeMessages(mMsg); 1153 mHandler.sendEmptyMessage(mMsg); 1154 return true; 1155 } 1156 return false; 1157 } 1158 } 1159 1160 private void setViewVisibility(View v, int vis, int anim) { 1161 if (v.getVisibility() != vis) { 1162 //Slog.d(TAG, "setViewVisibility vis=" + (vis == View.VISIBLE) + " v=" + v); 1163 v.setAnimation(AnimationUtils.loadAnimation(mContext, anim)); 1164 v.setVisibility(vis); 1165 } 1166 } 1167 1168 1169 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1170 pw.print("mDisabled=0x"); 1171 pw.println(Integer.toHexString(mDisabled)); 1172 } 1173} 1174 1175 1176