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