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