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