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