TabletStatusBar.java revision a31e4190cb44eaa51bb8dee7d715fcd72d6c0b03
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.Configuration; 32import android.content.res.Resources; 33import android.inputmethodservice.InputMethodService; 34import android.graphics.PixelFormat; 35import android.graphics.Rect; 36import android.graphics.drawable.LayerDrawable; 37import android.os.Handler; 38import android.os.IBinder; 39import android.os.Message; 40import android.os.RemoteException; 41import android.os.ServiceManager; 42import android.text.TextUtils; 43import android.util.Slog; 44import android.view.Gravity; 45import android.view.IWindowManager; 46import android.view.KeyEvent; 47import android.view.LayoutInflater; 48import android.view.MotionEvent; 49import android.view.VelocityTracker; 50import android.view.View; 51import android.view.ViewConfiguration; 52import android.view.ViewGroup; 53import android.view.WindowManager; 54import android.view.WindowManagerImpl; 55import android.widget.ImageView; 56import android.widget.LinearLayout; 57import android.widget.RemoteViews; 58import android.widget.ScrollView; 59import android.widget.TextView; 60 61import com.android.internal.statusbar.StatusBarIcon; 62import com.android.internal.statusbar.StatusBarNotification; 63 64import com.android.systemui.R; 65import com.android.systemui.statusbar.*; 66import com.android.systemui.statusbar.policy.BatteryController; 67import com.android.systemui.statusbar.policy.BluetoothController; 68import com.android.systemui.statusbar.policy.NetworkController; 69import com.android.systemui.recent.RecentApplicationsActivity; 70 71public class TabletStatusBar extends StatusBar implements 72 HeightReceiver.OnBarHeightChangedListener, 73 InputMethodsPanel.OnHardKeyboardEnabledChangeListener { 74 public static final boolean DEBUG = false; 75 public static final String TAG = "TabletStatusBar"; 76 77 public static final int MAX_NOTIFICATION_ICONS = 5; 78 // IME switcher icon is big and occupy width of two icons 79 public static final int MAX_NOTIFICATION_ICONS_IME_BUTTON_VISIBLE = MAX_NOTIFICATION_ICONS - 1; 80 81 public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; 82 public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; 83 public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002; 84 public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003; 85 public static final int MSG_OPEN_RECENTS_PANEL = 1020; 86 public static final int MSG_CLOSE_RECENTS_PANEL = 1021; 87 public static final int MSG_SHOW_CHROME = 1030; 88 public static final int MSG_HIDE_CHROME = 1031; 89 public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040; 90 public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041; 91 public static final int MSG_STOP_TICKER = 2000; 92 93 // Fitts' Law assistance for LatinIME; TODO: replace with a more general approach 94 private static final boolean FAKE_SPACE_BAR = true; 95 96 // The height of the bar, as definied by the build. It may be taller if we're plugged 97 // into hdmi. 98 int mNaturalBarHeight = -1; 99 int mIconSize = -1; 100 int mIconHPadding = -1; 101 102 H mHandler = new H(); 103 104 IWindowManager mWindowManager; 105 106 // tracking all current notifications 107 private NotificationData mNotns = new NotificationData(); 108 109 TabletStatusBarView mStatusBarView; 110 View mNotificationArea; 111 View mNotificationTrigger; 112 NotificationIconArea mNotificationIconArea; 113 View mNavigationArea; 114 115 ImageView mBackButton; 116 View mHomeButton; 117 View mMenuButton; 118 View mRecentButton; 119 120 ViewGroup mNotificationAndImeArea; 121 InputMethodButton mInputMethodSwitchButton; 122 123 NotificationPanel mNotificationPanel; 124 NotificationPeekPanel mNotificationPeekWindow; 125 ViewGroup mNotificationPeekRow; 126 int mNotificationPeekIndex; 127 IBinder mNotificationPeekKey; 128 LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight; 129 130 int mNotificationPeekTapDuration; 131 int mNotificationFlingVelocity; 132 133 ViewGroup mPile; 134 135 HeightReceiver mHeightReceiver; 136 BatteryController mBatteryController; 137 BluetoothController mBluetoothController; 138 NetworkController mNetworkController; 139 140 View mBarContents; 141 142 // hide system chrome ("lights out") support 143 View mShadow; 144 145 NotificationIconArea.IconLayout mIconLayout; 146 147 TabletTicker mTicker; 148 149 View mFakeSpaceBar; 150 KeyEvent mSpaceBarKeyEvent = null; 151 152 // for disabling the status bar 153 int mDisabled = 0; 154 155 boolean mNotificationsOn = true; 156 private RecentAppsPanel mRecentsPanel; 157 private InputMethodsPanel mInputMethodsPanel; 158 159 public Context getContext() { return mContext; } 160 161 protected void addPanelWindows() { 162 final Context context = mContext; 163 164 // Notification Panel 165 mNotificationPanel = (NotificationPanel)View.inflate(context, 166 R.layout.status_bar_notification_panel, null); 167 mNotificationPanel.show(false, false); 168 mNotificationPanel.setOnTouchListener( 169 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel)); 170 171 // the battery and network icons 172 mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery)); 173 mBatteryController.addLabelView( 174 (TextView)mNotificationPanel.findViewById(R.id.battery_text)); 175 mBluetoothController.addIconView( 176 (ImageView)mNotificationPanel.findViewById(R.id.bluetooth)); 177 mNetworkController.addCombinedSignalIconView( 178 (ImageView)mNotificationPanel.findViewById(R.id.network_signal)); 179 mNetworkController.addDataTypeIconView( 180 (ImageView)mNotificationPanel.findViewById(R.id.network_type)); 181 mNetworkController.addDataDirectionOverlayIconView( 182 (ImageView)mNotificationPanel.findViewById(R.id.network_direction)); 183 mNetworkController.addLabelView( 184 (TextView)mNotificationPanel.findViewById(R.id.network_text)); 185 mNetworkController.addLabelView( 186 (TextView)mBarContents.findViewById(R.id.network_text)); 187 188 mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel); 189 190 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 191 512, // ViewGroup.LayoutParams.MATCH_PARENT, 192 ViewGroup.LayoutParams.MATCH_PARENT, 193 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 194 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 195 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 196 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 197 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 198 PixelFormat.TRANSLUCENT); 199 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 200 lp.setTitle("NotificationPanel"); 201 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 202 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 203 lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation 204// lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade 205 206 WindowManagerImpl.getDefault().addView(mNotificationPanel, lp); 207 208 // Notification preview window 209 mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context, 210 R.layout.status_bar_notification_peek, null); 211 mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content); 212 mNotificationPeekWindow.setVisibility(View.GONE); 213 mNotificationPeekWindow.setOnTouchListener( 214 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow)); 215 mNotificationPeekScrubRight = new LayoutTransition(); 216 mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, 217 ObjectAnimator.ofInt(null, "left", -512, 0)); 218 mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, 219 ObjectAnimator.ofInt(null, "left", -512, 0)); 220 mNotificationPeekScrubRight.setDuration(500); 221 222 mNotificationPeekScrubLeft = new LayoutTransition(); 223 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, 224 ObjectAnimator.ofInt(null, "left", 512, 0)); 225 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, 226 ObjectAnimator.ofInt(null, "left", 512, 0)); 227 mNotificationPeekScrubLeft.setDuration(500); 228 229 // XXX: setIgnoreChildren? 230 lp = new WindowManager.LayoutParams( 231 512, // ViewGroup.LayoutParams.WRAP_CONTENT, 232 ViewGroup.LayoutParams.WRAP_CONTENT, 233 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 234 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 235 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 236 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 237 PixelFormat.TRANSLUCENT); 238 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 239 lp.setTitle("NotificationPeekWindow"); 240 lp.windowAnimations = com.android.internal.R.style.Animation_Toast; 241 242 WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp); 243 244 // Recents Panel 245 mRecentsPanel = (RecentAppsPanel) View.inflate(context, 246 R.layout.status_bar_recent_panel, null); 247 mRecentsPanel.setVisibility(View.GONE); 248 mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, 249 mRecentsPanel)); 250 mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel); 251 252 lp = new WindowManager.LayoutParams( 253 ViewGroup.LayoutParams.WRAP_CONTENT, 254 ViewGroup.LayoutParams.WRAP_CONTENT, 255 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 256 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 257 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 258 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 259 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 260 PixelFormat.TRANSLUCENT); 261 lp.gravity = Gravity.BOTTOM | Gravity.LEFT; 262 lp.setTitle("RecentsPanel"); 263 lp.windowAnimations = R.style.Animation_RecentPanel; 264 265 WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); 266 mRecentsPanel.setBar(this); 267 268 // Input methods Panel 269 mInputMethodsPanel = (InputMethodsPanel) View.inflate(context, 270 R.layout.status_bar_input_methods_panel, null); 271 mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this); 272 mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener( 273 MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel)); 274 mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton); 275 mStatusBarView.setIgnoreChildren(3, mInputMethodSwitchButton, mInputMethodsPanel); 276 lp = new WindowManager.LayoutParams( 277 ViewGroup.LayoutParams.WRAP_CONTENT, 278 ViewGroup.LayoutParams.WRAP_CONTENT, 279 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 280 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 281 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 282 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 283 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 284 PixelFormat.TRANSLUCENT); 285 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 286 lp.setTitle("InputMethodsPanel"); 287 lp.windowAnimations = R.style.Animation_RecentPanel; 288 289 WindowManagerImpl.getDefault().addView(mInputMethodsPanel, lp); 290 } 291 292 @Override 293 public void start() { 294 super.start(); // will add the main bar view 295 } 296 297 @Override 298 protected void onConfigurationChanged(Configuration newConfig) { 299 loadDimens(); 300 } 301 302 protected void loadDimens() { 303 final Resources res = mContext.getResources(); 304 305 mNaturalBarHeight = res.getDimensionPixelSize( 306 com.android.internal.R.dimen.status_bar_height); 307 308 int newIconSize = res.getDimensionPixelSize( 309 com.android.internal.R.dimen.status_bar_icon_size); 310 int newIconHPadding = res.getDimensionPixelSize( 311 R.dimen.status_bar_icon_padding); 312 313 if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { 314// Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); 315 mIconHPadding = newIconHPadding; 316 mIconSize = newIconSize; 317 reloadAllNotificationIcons(); // reload the tray 318 } 319 } 320 321 protected View makeStatusBarView() { 322 final Context context = mContext; 323 324 mWindowManager = IWindowManager.Stub.asInterface( 325 ServiceManager.getService(Context.WINDOW_SERVICE)); 326 327 // This guy will listen for HDMI plugged broadcasts so we can resize the 328 // status bar as appropriate. 329 mHeightReceiver = new HeightReceiver(mContext); 330 mHeightReceiver.registerReceiver(); 331 loadDimens(); 332 333 final TabletStatusBarView sb = (TabletStatusBarView)View.inflate( 334 context, R.layout.status_bar, null); 335 mStatusBarView = sb; 336 337 sb.setHandler(mHandler); 338 339 mBarContents = sb.findViewById(R.id.bar_contents); 340 341 // the whole right-hand side of the bar 342 mNotificationArea = sb.findViewById(R.id.notificationArea); 343 344 // the button to open the notification area 345 mNotificationTrigger = sb.findViewById(R.id.notificationTrigger); 346 mNotificationTrigger.setOnClickListener(mOnClickListener); 347 348 // the more notifications icon 349 mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons); 350 351 // where the icons go 352 mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons); 353 mIconLayout.setOnTouchListener(new NotificationIconTouchListener()); 354 355 ViewConfiguration vc = ViewConfiguration.get(context); 356 mNotificationPeekTapDuration = vc.getTapTimeout(); 357 mNotificationFlingVelocity = 300; // px/s 358 359 mTicker = new TabletTicker(this); 360 361 // The icons 362 mBatteryController = new BatteryController(mContext); 363 mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); 364 mBluetoothController = new BluetoothController(mContext); 365 mBluetoothController.addIconView((ImageView)sb.findViewById(R.id.bluetooth)); 366 mNetworkController = new NetworkController(mContext); 367 mNetworkController.addCombinedSignalIconView( 368 (ImageView)sb.findViewById(R.id.network_signal)); 369 mNetworkController.addDataTypeIconView( 370 (ImageView)sb.findViewById(R.id.network_type)); 371 mNetworkController.addDataDirectionOverlayIconView( 372 (ImageView)sb.findViewById(R.id.network_direction)); 373 374 // The navigation buttons 375 mBackButton = (ImageView)sb.findViewById(R.id.back); 376 mNavigationArea = sb.findViewById(R.id.navigationArea); 377 mHomeButton = mNavigationArea.findViewById(R.id.home); 378 mMenuButton = mNavigationArea.findViewById(R.id.menu); 379 mRecentButton = mNavigationArea.findViewById(R.id.recent_apps); 380 mRecentButton.setOnClickListener(mOnClickListener); 381 382 // The bar contents buttons 383 mNotificationAndImeArea = (ViewGroup)sb.findViewById(R.id.notificationAndImeArea); 384 mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton); 385 // Overwrite the lister 386 mInputMethodSwitchButton.setOnClickListener(mOnClickListener); 387 388 // for redirecting errant bar taps to the IME 389 mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar); 390 391 // "shadows" of the status bar features, for lights-out mode 392 mShadow = sb.findViewById(R.id.bar_shadow); 393 mShadow.setOnTouchListener( 394 new View.OnTouchListener() { 395 public boolean onTouch(View v, MotionEvent ev) { 396 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 397 // even though setting the systemUI visibility below will turn these views 398 // on, we need them to come up faster so that they can catch this motion 399 // event 400 mShadow.setVisibility(View.GONE); 401 mBarContents.setVisibility(View.VISIBLE); 402 403 try { 404 mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE); 405 } catch (RemoteException ex) { 406 // system process dead 407 } 408 } 409 return false; 410 } 411 }); 412 413 // tuning parameters 414 final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 600; 415 final int LIGHTS_GOING_OUT_SHADOW_DURATION = 1000; 416 final int LIGHTS_GOING_OUT_SHADOW_DELAY = 500; 417 418 final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200; 419// final int LIGHTS_COMING_UP_SYSBAR_DELAY = 50; 420 final int LIGHTS_COMING_UP_SHADOW_DURATION = 0; 421 422 LayoutTransition xition = new LayoutTransition(); 423 xition.setAnimator(LayoutTransition.APPEARING, 424 ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)); 425 xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION); 426 xition.setStartDelay(LayoutTransition.APPEARING, 0); 427 xition.setAnimator(LayoutTransition.DISAPPEARING, 428 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 429 xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION); 430 xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 431 ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition); 432 433 xition = new LayoutTransition(); 434 xition.setAnimator(LayoutTransition.APPEARING, 435 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); 436 xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION); 437 xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY); 438 xition.setAnimator(LayoutTransition.DISAPPEARING, 439 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 440 xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION); 441 xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 442 ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition); 443 444 // set the initial view visibility 445 setAreThereNotifications(); 446 447 // Add the windows 448 addPanelWindows(); 449 450 mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content); 451 mPile.removeAllViews(); 452 453 ScrollView scroller = (ScrollView)mPile.getParent(); 454 scroller.setFillViewport(true); 455 456 mHeightReceiver.addOnBarHeightChangedListener(this); 457 458 return sb; 459 } 460 461 public int getStatusBarHeight() { 462 return mHeightReceiver.getHeight(); 463 } 464 465 protected int getStatusBarGravity() { 466 return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL; 467 } 468 469 public void onBarHeightChanged(int height) { 470 final WindowManager.LayoutParams lp 471 = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams(); 472 if (lp == null) { 473 // haven't been added yet 474 return; 475 } 476 if (lp.height != height) { 477 lp.height = height; 478 final WindowManager wm = WindowManagerImpl.getDefault(); 479 wm.updateViewLayout(mStatusBarView, lp); 480 } 481 } 482 483 private class H extends Handler { 484 public void handleMessage(Message m) { 485 switch (m.what) { 486 case MSG_OPEN_NOTIFICATION_PEEK: 487 if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1); 488 if (m.arg1 >= 0) { 489 final int N = mNotns.size(); 490 if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { 491 NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex); 492 entry.icon.setBackgroundColor(0); 493 mNotificationPeekIndex = -1; 494 mNotificationPeekKey = null; 495 } 496 497 final int peekIndex = m.arg1; 498 if (peekIndex < N) { 499 //Slog.d(TAG, "loading peek: " + peekIndex); 500 NotificationData.Entry entry = mNotns.get(N-1-peekIndex); 501 NotificationData.Entry copy = new NotificationData.Entry( 502 entry.key, 503 entry.notification, 504 entry.icon); 505 inflateViews(copy, mNotificationPeekRow); 506 507 entry.icon.setBackgroundColor(0x20FFFFFF); 508 509// mNotificationPeekRow.setLayoutTransition( 510// peekIndex < mNotificationPeekIndex 511// ? mNotificationPeekScrubLeft 512// : mNotificationPeekScrubRight); 513 514 mNotificationPeekRow.removeAllViews(); 515 mNotificationPeekRow.addView(copy.row); 516 517 mNotificationPeekWindow.setVisibility(View.VISIBLE); 518 mNotificationPanel.show(false, true); 519 520 mNotificationPeekIndex = peekIndex; 521 mNotificationPeekKey = entry.key; 522 } 523 } 524 break; 525 case MSG_CLOSE_NOTIFICATION_PEEK: 526 if (DEBUG) Slog.d(TAG, "closing notification peek window"); 527 mNotificationPeekWindow.setVisibility(View.GONE); 528 mNotificationPeekRow.removeAllViews(); 529 final int N = mNotns.size(); 530 if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { 531 NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex); 532 entry.icon.setBackgroundColor(0); 533 } 534 535 mNotificationPeekIndex = -1; 536 mNotificationPeekKey = null; 537 break; 538 case MSG_OPEN_NOTIFICATION_PANEL: 539 if (DEBUG) Slog.d(TAG, "opening notifications panel"); 540 if (!mNotificationPanel.isShowing()) { 541 mNotificationPeekWindow.setVisibility(View.GONE); 542 mNotificationPanel.show(true, true); 543 mNotificationArea.setVisibility(View.GONE); 544 mTicker.halt(); 545 } 546 break; 547 case MSG_CLOSE_NOTIFICATION_PANEL: 548 if (DEBUG) Slog.d(TAG, "closing notifications panel"); 549 if (mNotificationPanel.isShowing()) { 550 mNotificationPanel.show(false, true); 551 mNotificationArea.setVisibility(View.VISIBLE); 552 } 553 break; 554 case MSG_OPEN_RECENTS_PANEL: 555 if (DEBUG) Slog.d(TAG, "opening recents panel"); 556 if (mRecentsPanel != null) { 557 mRecentsPanel.setVisibility(View.VISIBLE); 558 mRecentsPanel.show(true, true); 559 } 560 break; 561 case MSG_CLOSE_RECENTS_PANEL: 562 if (DEBUG) Slog.d(TAG, "closing recents panel"); 563 if (mRecentsPanel != null && mRecentsPanel.isShowing()) { 564 mRecentsPanel.show(false, true); 565 } 566 break; 567 case MSG_OPEN_INPUT_METHODS_PANEL: 568 if (DEBUG) Slog.d(TAG, "opening input methods panel"); 569 if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel(); 570 break; 571 case MSG_CLOSE_INPUT_METHODS_PANEL: 572 if (DEBUG) Slog.d(TAG, "closing input methods panel"); 573 if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false); 574 break; 575 case MSG_SHOW_CHROME: 576 if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)"); 577 mBarContents.setVisibility(View.VISIBLE); 578 mShadow.setVisibility(View.GONE); 579 notifyLightsChanged(true); 580 break; 581 case MSG_HIDE_CHROME: 582 if (DEBUG) Slog.d(TAG, "showing shadows (lights out)"); 583 animateCollapse(); 584 mBarContents.setVisibility(View.GONE); 585 mShadow.setVisibility(View.VISIBLE); 586 notifyLightsChanged(false); 587 break; 588 case MSG_STOP_TICKER: 589 mTicker.halt(); 590 break; 591 } 592 } 593 } 594 595 private void notifyLightsChanged(boolean shown) { 596 try { 597 Slog.d(TAG, "lights " + (shown?"on":"out")); 598 mWindowManager.statusBarVisibilityChanged( 599 shown ? View.STATUS_BAR_VISIBLE : View.STATUS_BAR_HIDDEN); 600 } catch (RemoteException ex) { 601 } 602 } 603 604 public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 605 if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon); 606 } 607 608 public void updateIcon(String slot, int index, int viewIndex, 609 StatusBarIcon old, StatusBarIcon icon) { 610 if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon); 611 } 612 613 public void removeIcon(String slot, int index, int viewIndex) { 614 if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")"); 615 } 616 617 public void addNotification(IBinder key, StatusBarNotification notification) { 618 if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); 619 addNotificationViews(key, notification); 620 621 final boolean immersive = isImmersive(); 622 if (false && immersive) { 623 // TODO: immersive mode popups for tablet 624 } else if (notification.notification.fullScreenIntent != null) { 625 // not immersive & a full-screen alert should be shown 626 Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;" 627 + " sending fullScreenIntent"); 628 try { 629 notification.notification.fullScreenIntent.send(); 630 } catch (PendingIntent.CanceledException e) { 631 } 632 } else { 633 tick(key, notification, true); 634 } 635 636 setAreThereNotifications(); 637 } 638 639 public void updateNotification(IBinder key, StatusBarNotification notification) { 640 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ") // TODO"); 641 642 final NotificationData.Entry oldEntry = mNotns.findByKey(key); 643 if (oldEntry == null) { 644 Slog.w(TAG, "updateNotification for unknown key: " + key); 645 return; 646 } 647 648 final StatusBarNotification oldNotification = oldEntry.notification; 649 final RemoteViews oldContentView = oldNotification.notification.contentView; 650 651 final RemoteViews contentView = notification.notification.contentView; 652 653 if (DEBUG) { 654 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 655 + " ongoing=" + oldNotification.isOngoing() 656 + " expanded=" + oldEntry.expanded 657 + " contentView=" + oldContentView); 658 Slog.d(TAG, "new notification: when=" + notification.notification.when 659 + " ongoing=" + oldNotification.isOngoing() 660 + " contentView=" + contentView); 661 } 662 663 // Can we just reapply the RemoteViews in place? If when didn't change, the order 664 // didn't change. 665 boolean contentsUnchanged = oldEntry.expanded != null 666 && contentView != null && oldContentView != null 667 && contentView.getPackage() != null 668 && oldContentView.getPackage() != null 669 && oldContentView.getPackage().equals(contentView.getPackage()) 670 && oldContentView.getLayoutId() == contentView.getLayoutId(); 671 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 672 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 673 && notification.priority == oldNotification.priority; 674 // priority now encompasses isOngoing() 675 boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1; 676 if (contentsUnchanged && (orderUnchanged || isLastAnyway)) { 677 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 678 oldEntry.notification = notification; 679 try { 680 // Reapply the RemoteViews 681 contentView.reapply(mContext, oldEntry.content); 682 // update the contentIntent 683 final PendingIntent contentIntent = notification.notification.contentIntent; 684 if (contentIntent != null) { 685 oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent, 686 notification.pkg, notification.tag, notification.id)); 687 } else { 688 oldEntry.content.setOnClickListener(null); 689 } 690 // Update the icon. 691 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 692 notification.notification.icon, notification.notification.iconLevel, 693 notification.notification.number); 694 if (!oldEntry.icon.set(ic)) { 695 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 696 return; 697 } 698 // Update the large icon 699 if (notification.notification.largeIcon != null) { 700 oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon); 701 } else { 702 oldEntry.largeIcon.getLayoutParams().width = 0; 703 oldEntry.largeIcon.setVisibility(View.INVISIBLE); 704 } 705 706 if (key == mNotificationPeekKey) { 707 // must update the peek window 708 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 709 peekMsg.arg1 = mNotificationPeekIndex; 710 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 711 mHandler.sendMessage(peekMsg); 712 } 713 } 714 catch (RuntimeException e) { 715 // It failed to add cleanly. Log, and remove the view from the panel. 716 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 717 removeNotificationViews(key); 718 addNotificationViews(key, notification); 719 } 720 } else { 721 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 722 removeNotificationViews(key); 723 addNotificationViews(key, notification); 724 } 725 // fullScreenIntent doesn't happen on updates. You need to clear & repost a new 726 // notification. 727 final boolean immersive = isImmersive(); 728 if (false && immersive) { 729 // TODO: immersive mode 730 } else { 731 tick(key, notification, false); 732 } 733 734 setAreThereNotifications(); 735 } 736 737 public void removeNotification(IBinder key) { 738 if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO"); 739 removeNotificationViews(key); 740 mTicker.remove(key); 741 setAreThereNotifications(); 742 } 743 744 public void showClock(boolean show) { 745 View clock = mBarContents.findViewById(R.id.clock); 746 View network_text = mBarContents.findViewById(R.id.network_text); 747 if (clock != null) { 748 clock.setVisibility(show ? View.VISIBLE : View.GONE); 749 } 750 if (network_text != null) { 751 network_text.setVisibility((!show) ? View.VISIBLE : View.GONE); 752 } 753 } 754 755 public void disable(int state) { 756 int old = mDisabled; 757 int diff = state ^ old; 758 mDisabled = state; 759 760 // act accordingly 761 if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { 762 boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; 763 Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes")); 764 showClock(show); 765 } 766 if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { 767 boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0; 768 Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes")); 769 mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE); 770 } 771 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 772 if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { 773 Slog.i(TAG, "DISABLE_EXPAND: yes"); 774 animateCollapse(); 775 } 776 } 777 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 778 if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 779 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); 780 // synchronize with current shadow state 781 mNotificationIconArea.setVisibility(View.GONE); 782 mTicker.halt(); 783 } else { 784 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no"); 785 // synchronize with current shadow state 786 mNotificationIconArea.setVisibility(View.VISIBLE); 787 } 788 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 789 if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 790 mTicker.halt(); 791 } 792 } 793 if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) { 794 if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) { 795 Slog.i(TAG, "DISABLE_NAVIGATION: yes"); 796 mNavigationArea.setVisibility(View.GONE); 797 mInputMethodSwitchButton.setScreenLocked(true); 798 } else { 799 Slog.i(TAG, "DISABLE_NAVIGATION: no"); 800 mNavigationArea.setVisibility(View.VISIBLE); 801 mInputMethodSwitchButton.setScreenLocked(false); 802 } 803 } 804 if ((diff & StatusBarManager.DISABLE_BACK) != 0) { 805 if ((state & StatusBarManager.DISABLE_BACK) != 0) { 806 Slog.i(TAG, "DISABLE_BACK: yes"); 807 mBackButton.setVisibility(View.INVISIBLE); 808 mInputMethodSwitchButton.setScreenLocked(true); 809 } else { 810 Slog.i(TAG, "DISABLE_BACK: no"); 811 mBackButton.setVisibility(View.VISIBLE); 812 mInputMethodSwitchButton.setScreenLocked(false); 813 } 814 } 815 816 } 817 818 private boolean hasTicker(Notification n) { 819 return n.tickerView != null || !TextUtils.isEmpty(n.tickerText); 820 } 821 822 private void tick(IBinder key, StatusBarNotification n, boolean firstTime) { 823 // Don't show the ticker when the windowshade is open. 824 if (mNotificationPanel.isShowing()) { 825 return; 826 } 827 // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification 828 // if it's a new notification. 829 if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) { 830 return; 831 } 832 // Show the ticker if one is requested. Also don't do this 833 // until status bar window is attached to the window manager, 834 // because... well, what's the point otherwise? And trying to 835 // run a ticker without being attached will crash! 836 if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) { 837 if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS 838 | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { 839 mTicker.add(key, n); 840 mNotificationAndImeArea.setVisibility(View.GONE); 841 } 842 } 843 } 844 845 // called by TabletTicker when it's done with all queued ticks 846 public void doneTicking() { 847 mNotificationAndImeArea.setVisibility(View.VISIBLE); 848 } 849 850 public void animateExpand() { 851 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 852 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 853 } 854 855 public void animateCollapse() { 856 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); 857 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); 858 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 859 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 860 mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL); 861 mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL); 862 } 863 864 // called by StatusBar 865 @Override 866 public void setLightsOn(boolean on) { 867 // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app 868 // that can't handle lights-out mode. 869 if (mMenuButton.getVisibility() == View.VISIBLE) { 870 on = true; 871 } 872 mHandler.removeMessages(MSG_HIDE_CHROME); 873 mHandler.removeMessages(MSG_SHOW_CHROME); 874 mHandler.sendEmptyMessage(on ? MSG_SHOW_CHROME : MSG_HIDE_CHROME); 875 } 876 877 public void setMenuKeyVisible(boolean visible) { 878 if (DEBUG) { 879 Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button"); 880 } 881 mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE); 882 883 // See above re: lights-out policy for legacy apps. 884 if (visible) setLightsOn(true); 885 } 886 887 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { 888 mInputMethodSwitchButton.setImeWindowStatus(token, 889 (vis & InputMethodService.IME_ACTIVE) != 0); 890 updateNotificationIcons(); 891 mInputMethodsPanel.setImeToken(token); 892 int res; 893 switch (backDisposition) { 894 case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: 895 res = R.drawable.ic_sysbar_back; 896 break; 897 case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: 898 res = R.drawable.ic_sysbar_back_ime; 899 break; 900 case InputMethodService.BACK_DISPOSITION_DEFAULT: 901 default: 902 if ((vis & InputMethodService.IME_VISIBLE) != 0) { 903 res = R.drawable.ic_sysbar_back_ime; 904 } else { 905 res = R.drawable.ic_sysbar_back; 906 } 907 break; 908 } 909 mBackButton.setImageResource(res); 910 if (FAKE_SPACE_BAR) { 911 mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0) 912 ? View.VISIBLE : View.GONE); 913 } 914 } 915 916 @Override 917 public void setHardKeyboardStatus(boolean available, boolean enabled) { 918 if (DEBUG) { 919 Slog.d(TAG, "Set hard keyboard status: available=" + available 920 + ", enabled=" + enabled); 921 } 922 mInputMethodSwitchButton.setHardKeyboardStatus(available); 923 updateNotificationIcons(); 924 mInputMethodsPanel.setHardKeyboardStatus(available, enabled); 925 } 926 927 @Override 928 public void onHardKeyboardEnabledChange(boolean enabled) { 929 try { 930 mBarService.setHardKeyboardEnabled(enabled); 931 } catch (RemoteException ex) { 932 } 933 } 934 935 private boolean isImmersive() { 936 try { 937 return ActivityManagerNative.getDefault().isTopActivityImmersive(); 938 //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 939 } catch (RemoteException ex) { 940 // the end is nigh 941 return false; 942 } 943 } 944 945 private void setAreThereNotifications() { 946 final boolean hasClearable = mNotns.hasClearableItems(); 947 } 948 949 /** 950 * Cancel this notification and tell the status bar service about the failure. Hold no locks. 951 */ 952 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 953 removeNotification(key); 954 try { 955 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 956 } catch (RemoteException ex) { 957 // The end is nigh. 958 } 959 } 960 961 private void sendKey(KeyEvent key) { 962 try { 963 if (DEBUG) Slog.d(TAG, "injecting key event: " + key); 964 mWindowManager.injectInputEventNoWait(key); 965 } catch (RemoteException ex) { 966 } 967 } 968 969 private View.OnClickListener mOnClickListener = new View.OnClickListener() { 970 public void onClick(View v) { 971 if (v == mNotificationTrigger) { 972 onClickNotificationTrigger(); 973 } else if (v == mRecentButton) { 974 onClickRecentButton(); 975 } else if (v == mInputMethodSwitchButton) { 976 onClickInputMethodSwitchButton(); 977 } 978 } 979 }; 980 981 public void onClickNotificationTrigger() { 982 if (DEBUG) Slog.d(TAG, "clicked notification icons; disabled=" + mDisabled); 983 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 984 if (!mNotificationsOn) { 985 mNotificationsOn = true; 986 mIconLayout.setVisibility(View.VISIBLE); // TODO: animation 987 } else { 988 int msg = !mNotificationPanel.isShowing() 989 ? MSG_OPEN_NOTIFICATION_PANEL 990 : MSG_CLOSE_NOTIFICATION_PANEL; 991 mHandler.removeMessages(msg); 992 mHandler.sendEmptyMessage(msg); 993 } 994 } 995 } 996 997 public void onClickRecentButton() { 998 if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); 999 if (mRecentsPanel == null) { 1000 Intent intent = new Intent(); 1001 intent.setClass(mContext, RecentApplicationsActivity.class); 1002 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1003 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 1004 mContext.startActivity(intent); 1005 } else { 1006 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 1007 int msg = (mRecentsPanel.getVisibility() == View.GONE) 1008 ? MSG_OPEN_RECENTS_PANEL 1009 : MSG_CLOSE_RECENTS_PANEL; 1010 mHandler.removeMessages(msg); 1011 mHandler.sendEmptyMessage(msg); 1012 } 1013 } 1014 } 1015 1016 public void onClickInputMethodSwitchButton() { 1017 if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled); 1018 int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ? 1019 MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL; 1020 mHandler.removeMessages(msg); 1021 mHandler.sendEmptyMessage(msg); 1022 } 1023 1024 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 1025 return new NotificationClicker(intent, pkg, tag, id); 1026 } 1027 1028 private class NotificationClicker implements View.OnClickListener { 1029 private PendingIntent mIntent; 1030 private String mPkg; 1031 private String mTag; 1032 private int mId; 1033 1034 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 1035 mIntent = intent; 1036 mPkg = pkg; 1037 mTag = tag; 1038 mId = id; 1039 } 1040 1041 public void onClick(View v) { 1042 try { 1043 // The intent we are sending is for the application, which 1044 // won't have permission to immediately start an activity after 1045 // the user switches to home. We know it is safe to do at this 1046 // point, so make sure new activity switches are now allowed. 1047 ActivityManagerNative.getDefault().resumeAppSwitches(); 1048 } catch (RemoteException e) { 1049 } 1050 1051 if (mIntent != null) { 1052 int[] pos = new int[2]; 1053 v.getLocationOnScreen(pos); 1054 Intent overlay = new Intent(); 1055 overlay.setSourceBounds( 1056 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 1057 try { 1058 mIntent.send(mContext, 0, overlay); 1059 } catch (PendingIntent.CanceledException e) { 1060 // the stack trace isn't very helpful here. Just log the exception message. 1061 Slog.w(TAG, "Sending contentIntent failed: " + e); 1062 } 1063 } 1064 1065 try { 1066 mBarService.onNotificationClick(mPkg, mTag, mId); 1067 } catch (RemoteException ex) { 1068 // system process is dead if we're here. 1069 } 1070 1071 // close the shade if it was open 1072 animateCollapse(); 1073 1074 // If this click was on the intruder alert, hide that instead 1075// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 1076 } 1077 } 1078 1079 StatusBarNotification removeNotificationViews(IBinder key) { 1080 NotificationData.Entry entry = mNotns.remove(key); 1081 if (entry == null) { 1082 Slog.w(TAG, "removeNotification for unknown key: " + key); 1083 return null; 1084 } 1085 // Remove the expanded view. 1086 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 1087 if (rowParent != null) rowParent.removeView(entry.row); 1088 1089 if (key == mNotificationPeekKey) { 1090 // must close the peek as well, since it's gone 1091 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1092 } 1093 // Remove the icon. 1094// ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); 1095// if (iconParent != null) iconParent.removeView(entry.icon); 1096 updateNotificationIcons(); 1097 1098 return entry.notification; 1099 } 1100 1101 private class NotificationIconTouchListener implements View.OnTouchListener { 1102 VelocityTracker mVT; 1103 1104 public NotificationIconTouchListener() { 1105 } 1106 1107 public boolean onTouch(View v, MotionEvent event) { 1108 boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; 1109 boolean panelShowing = mNotificationPanel.isShowing(); 1110 if (panelShowing) return false; 1111 1112 switch (event.getAction()) { 1113 case MotionEvent.ACTION_DOWN: 1114 mVT = VelocityTracker.obtain(); 1115 1116 // fall through 1117 case MotionEvent.ACTION_OUTSIDE: 1118 case MotionEvent.ACTION_MOVE: 1119 // peek and switch icons if necessary 1120 int numIcons = mIconLayout.getChildCount(); 1121 int peekIndex = (int)((float)event.getX() * numIcons / mIconLayout.getWidth()); 1122 if (peekIndex > numIcons - 1) peekIndex = numIcons - 1; 1123 else if (peekIndex < 0) peekIndex = 0; 1124 1125 if (!peeking || mNotificationPeekIndex != peekIndex) { 1126 if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex); 1127 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 1128 peekMsg.arg1 = peekIndex; 1129 1130 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1131 1132 // no delay if we're scrubbing left-right 1133 mHandler.sendMessage(peekMsg); 1134 } 1135 1136 // check for fling 1137 if (mVT != null) { 1138 mVT.addMovement(event); 1139 mVT.computeCurrentVelocity(1000); 1140 // require a little more oomph once we're already in peekaboo mode 1141 if (!panelShowing && ( 1142 (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) 1143 || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { 1144 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1145 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 1146 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1147 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 1148 } 1149 } 1150 return true; 1151 case MotionEvent.ACTION_UP: 1152 case MotionEvent.ACTION_CANCEL: 1153 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1154 if (peeking) { 1155 mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 5000); 1156 } 1157 mVT.recycle(); 1158 mVT = null; 1159 return true; 1160 } 1161 return false; 1162 } 1163 } 1164 1165 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 1166 if (DEBUG) { 1167 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 1168 } 1169 // Construct the icon. 1170 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1171 notification.pkg + "/0x" + Integer.toHexString(notification.id)); 1172 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1173 1174 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 1175 notification.notification.icon, 1176 notification.notification.iconLevel, 1177 notification.notification.number); 1178 if (!iconView.set(ic)) { 1179 handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); 1180 return null; 1181 } 1182 // Construct the expanded view. 1183 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 1184 if (!inflateViews(entry, mPile)) { 1185 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 1186 + notification); 1187 return null; 1188 } 1189 1190 // Add the icon. 1191 int pos = mNotns.add(entry); 1192 if (DEBUG) { 1193 Slog.d(TAG, "addNotificationViews: added at " + pos); 1194 } 1195 updateNotificationIcons(); 1196 1197 return iconView; 1198 } 1199 1200 private void reloadAllNotificationIcons() { 1201 if (mIconLayout == null) return; 1202 mIconLayout.removeAllViews(); 1203 updateNotificationIcons(); 1204 } 1205 1206 private void updateNotificationIcons() { 1207 // XXX: need to implement a new limited linear layout class 1208 // to avoid removing & readding everything 1209 1210 if (mIconLayout == null) return; 1211 1212 final LinearLayout.LayoutParams params 1213 = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); 1214 1215 int N = mNotns.size(); 1216 1217 if (DEBUG) { 1218 Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); 1219 } 1220 1221 ArrayList<View> toShow = new ArrayList<View>(); 1222 1223 // When IME button is visible, the number of notification icons should be decremented 1224 // to fit the upper limit. 1225 final int maxNotificationIconsCount = 1226 (mInputMethodSwitchButton.getVisibility() != View.GONE) ? 1227 MAX_NOTIFICATION_ICONS_IME_BUTTON_VISIBLE : MAX_NOTIFICATION_ICONS; 1228 for (int i=0; i< maxNotificationIconsCount; i++) { 1229 if (i>=N) break; 1230 toShow.add(mNotns.get(N-i-1).icon); 1231 } 1232 1233 ArrayList<View> toRemove = new ArrayList<View>(); 1234 for (int i=0; i<mIconLayout.getChildCount(); i++) { 1235 View child = mIconLayout.getChildAt(i); 1236 if (!toShow.contains(child)) { 1237 toRemove.add(child); 1238 } 1239 } 1240 1241 for (View remove : toRemove) { 1242 mIconLayout.removeView(remove); 1243 } 1244 1245 for (int i=0; i<toShow.size(); i++) { 1246 View v = toShow.get(i); 1247 v.setPadding(mIconHPadding, 0, mIconHPadding, 0); 1248 if (v.getParent() == null) { 1249 mIconLayout.addView(v, i, params); 1250 } 1251 } 1252 1253 loadNotificationPanel(); 1254 } 1255 1256 private void loadNotificationPanel() { 1257 int N = mNotns.size(); 1258 1259 ArrayList<View> toShow = new ArrayList<View>(); 1260 1261 for (int i=0; i<N; i++) { 1262 View row = mNotns.get(N-i-1).row; 1263 toShow.add(row); 1264 } 1265 1266 ArrayList<View> toRemove = new ArrayList<View>(); 1267 for (int i=0; i<mPile.getChildCount(); i++) { 1268 View child = mPile.getChildAt(i); 1269 if (!toShow.contains(child)) { 1270 toRemove.add(child); 1271 } 1272 } 1273 1274 for (View remove : toRemove) { 1275 mPile.removeView(remove); 1276 } 1277 1278 for (int i=0; i<toShow.size(); i++) { 1279 View v = toShow.get(i); 1280 if (v.getParent() == null) { 1281 mPile.addView(v, N-1-i); // the notification panel has newest at the bottom 1282 } 1283 } 1284 1285 mNotificationPanel.setNotificationCount(N); 1286 } 1287 1288 void workAroundBadLayerDrawableOpacity(View v) { 1289 LayerDrawable d = (LayerDrawable)v.getBackground(); 1290 if (d == null) return; 1291 v.setBackgroundDrawable(null); 1292 d.setOpacity(PixelFormat.TRANSLUCENT); 1293 v.setBackgroundDrawable(d); 1294 } 1295 1296 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 1297 StatusBarNotification sbn = entry.notification; 1298 RemoteViews remoteViews = sbn.notification.contentView; 1299 if (remoteViews == null) { 1300 return false; 1301 } 1302 1303 // create the row view 1304 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 1305 Context.LAYOUT_INFLATER_SERVICE); 1306 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 1307 workAroundBadLayerDrawableOpacity(row); 1308 View vetoButton = row.findViewById(R.id.veto); 1309 if (entry.notification.isClearable()) { 1310 final String _pkg = sbn.pkg; 1311 final String _tag = sbn.tag; 1312 final int _id = sbn.id; 1313 vetoButton.setOnClickListener(new View.OnClickListener() { 1314 public void onClick(View v) { 1315 try { 1316 mBarService.onNotificationClear(_pkg, _tag, _id); 1317 } catch (RemoteException ex) { 1318 // system process is dead if we're here. 1319 } 1320 } 1321 }); 1322 } else { 1323 if ((sbn.notification.flags & Notification.FLAG_ONGOING_EVENT) == 0) { 1324 vetoButton.setVisibility(View.INVISIBLE); 1325 } else { 1326 vetoButton.setVisibility(View.GONE); 1327 } 1328 } 1329 1330 // the large icon 1331 ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); 1332 if (sbn.notification.largeIcon != null) { 1333 largeIcon.setImageBitmap(sbn.notification.largeIcon); 1334 } else { 1335 largeIcon.getLayoutParams().width = 0; 1336 largeIcon.setVisibility(View.INVISIBLE); 1337 } 1338 1339 // bind the click event to the content area 1340 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 1341 // XXX: update to allow controls within notification views 1342 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1343// content.setOnFocusChangeListener(mFocusChangeListener); 1344 PendingIntent contentIntent = sbn.notification.contentIntent; 1345 if (contentIntent != null) { 1346 content.setOnClickListener(new NotificationClicker(contentIntent, 1347 sbn.pkg, sbn.tag, sbn.id)); 1348 } else { 1349 content.setOnClickListener(null); 1350 } 1351 1352 View expanded = null; 1353 Exception exception = null; 1354 try { 1355 expanded = remoteViews.apply(mContext, content); 1356 } 1357 catch (RuntimeException e) { 1358 exception = e; 1359 } 1360 if (expanded == null) { 1361 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 1362 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 1363 return false; 1364 } else { 1365 content.addView(expanded); 1366 row.setDrawingCacheEnabled(true); 1367 } 1368 1369 entry.row = row; 1370 entry.content = content; 1371 entry.expanded = expanded; 1372 entry.largeIcon = largeIcon; 1373 1374 return true; 1375 } 1376 1377/* 1378 public class ShadowController { 1379 boolean mShowShadows; 1380 Map<View, View> mShadowsForElements = new IdentityHashMap<View, View>(7); 1381 Map<View, View> mElementsForShadows = new IdentityHashMap<View, View>(7); 1382 LayoutTransition mElementTransition, mShadowTransition; 1383 1384 View mTouchTarget; 1385 1386 ShadowController(boolean showShadows) { 1387 mShowShadows = showShadows; 1388 mTouchTarget = null; 1389 1390 mElementTransition = new LayoutTransition(); 1391// AnimatorSet s = new AnimatorSet(); 1392// s.play(ObjectAnimator.ofInt(null, "top", 48, 0)) 1393// .with(ObjectAnimator.ofFloat(null, "scaleY", 0.5f, 1f)) 1394// .with(ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)) 1395// ; 1396 mElementTransition.setAnimator(LayoutTransition.APPEARING, //s); 1397 ObjectAnimator.ofInt(null, "top", 48, 0)); 1398 mElementTransition.setDuration(LayoutTransition.APPEARING, 100); 1399 mElementTransition.setStartDelay(LayoutTransition.APPEARING, 0); 1400 1401// s = new AnimatorSet(); 1402// s.play(ObjectAnimator.ofInt(null, "top", 0, 48)) 1403// .with(ObjectAnimator.ofFloat(null, "scaleY", 1f, 0.5f)) 1404// .with(ObjectAnimator.ofFloat(null, "alpha", 1f, 0.5f)) 1405// ; 1406 mElementTransition.setAnimator(LayoutTransition.DISAPPEARING, //s); 1407 ObjectAnimator.ofInt(null, "top", 0, 48)); 1408 mElementTransition.setDuration(LayoutTransition.DISAPPEARING, 400); 1409 1410 mShadowTransition = new LayoutTransition(); 1411 mShadowTransition.setAnimator(LayoutTransition.APPEARING, 1412 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); 1413 mShadowTransition.setDuration(LayoutTransition.APPEARING, 200); 1414 mShadowTransition.setStartDelay(LayoutTransition.APPEARING, 100); 1415 mShadowTransition.setAnimator(LayoutTransition.DISAPPEARING, 1416 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 1417 mShadowTransition.setDuration(LayoutTransition.DISAPPEARING, 100); 1418 1419 ViewGroup bar = (ViewGroup) TabletStatusBar.this.mBarContents; 1420 bar.setLayoutTransition(mElementTransition); 1421 ViewGroup nav = (ViewGroup) TabletStatusBar.this.mNavigationArea; 1422 nav.setLayoutTransition(mElementTransition); 1423 ViewGroup shadowGroup = (ViewGroup) bar.findViewById(R.id.shadows); 1424 shadowGroup.setLayoutTransition(mShadowTransition); 1425 } 1426 1427 public void add(View element, View shadow) { 1428 shadow.setOnTouchListener(makeTouchListener()); 1429 mShadowsForElements.put(element, shadow); 1430 mElementsForShadows.put(shadow, element); 1431 } 1432 1433 public boolean getShadowState() { 1434 return mShowShadows; 1435 } 1436 1437 public View.OnTouchListener makeTouchListener() { 1438 return new View.OnTouchListener() { 1439 public boolean onTouch(View v, MotionEvent ev) { 1440 final int action = ev.getAction(); 1441 1442 if (DEBUG) Slog.d(TAG, "ShadowController: v=" + v + ", ev=" + ev); 1443 1444 // currently redirecting events? 1445 if (mTouchTarget == null) { 1446 mTouchTarget = mElementsForShadows.get(v); 1447 } 1448 1449 if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) { 1450 boolean last = false; 1451 switch (action) { 1452 case MotionEvent.ACTION_CANCEL: 1453 case MotionEvent.ACTION_UP: 1454 mHandler.removeMessages(MSG_RESTORE_SHADOWS); 1455 if (mShowShadows) { 1456 mHandler.sendEmptyMessageDelayed(MSG_RESTORE_SHADOWS, 1457 v == mNotificationShadow ? 5000 : 500); 1458 } 1459 last = true; 1460 break; 1461 case MotionEvent.ACTION_DOWN: 1462 mHandler.removeMessages(MSG_RESTORE_SHADOWS); 1463 setElementShadow(mTouchTarget, false); 1464 break; 1465 } 1466 mTouchTarget.dispatchTouchEvent(ev); 1467 if (last) mTouchTarget = null; 1468 return true; 1469 } 1470 1471 return false; 1472 } 1473 }; 1474 } 1475 1476 public void refresh() { 1477 for (View element : mShadowsForElements.keySet()) { 1478 setElementShadow(element, mShowShadows); 1479 } 1480 } 1481 1482 public void showAllShadows() { 1483 mShowShadows = true; 1484 refresh(); 1485 } 1486 1487 public void hideAllShadows() { 1488 mShowShadows = false; 1489 refresh(); 1490 } 1491 1492 // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are 1493 // disabled (and should not be shadowed or re-shown) 1494 public void setElementShadow(View button, boolean shade) { 1495 View shadow = mShadowsForElements.get(button); 1496 if (shadow != null) { 1497 if (button.getVisibility() != View.GONE) { 1498 shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE); 1499 button.setVisibility(shade ? View.INVISIBLE : View.VISIBLE); 1500 } 1501 } 1502 } 1503 1504 // Hide both element and shadow, using default layout animations. 1505 public void hideElement(View button) { 1506 Slog.d(TAG, "hiding: " + button); 1507 View shadow = mShadowsForElements.get(button); 1508 if (shadow != null) { 1509 shadow.setVisibility(View.GONE); 1510 } 1511 button.setVisibility(View.GONE); 1512 } 1513 1514 // Honoring the current shadow state. 1515 public void showElement(View button) { 1516 Slog.d(TAG, "showing: " + button); 1517 View shadow = mShadowsForElements.get(button); 1518 if (shadow != null) { 1519 shadow.setVisibility(mShowShadows ? View.VISIBLE : View.INVISIBLE); 1520 } 1521 button.setVisibility(mShowShadows ? View.INVISIBLE : View.VISIBLE); 1522 } 1523 } 1524 */ 1525 1526 public class TouchOutsideListener implements View.OnTouchListener { 1527 private int mMsg; 1528 private StatusBarPanel mPanel; 1529 1530 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1531 mMsg = msg; 1532 mPanel = panel; 1533 } 1534 1535 public boolean onTouch(View v, MotionEvent ev) { 1536 final int action = ev.getAction(); 1537 if (action == MotionEvent.ACTION_OUTSIDE 1538 || (action == MotionEvent.ACTION_DOWN 1539 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1540 mHandler.removeMessages(mMsg); 1541 mHandler.sendEmptyMessage(mMsg); 1542 return true; 1543 } 1544 return false; 1545 } 1546 } 1547 1548 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1549 pw.print("mDisabled=0x"); 1550 pw.println(Integer.toHexString(mDisabled)); 1551 pw.println("mNetworkController:"); 1552 mNetworkController.dump(fd, pw, args); 1553 } 1554} 1555 1556 1557