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