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