TabletStatusBar.java revision 6cf14a02bf9e499a8e6fd7e1cf79b2e9d16e9811
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.KeyguardManager; 27import android.app.Notification; 28import android.app.PendingIntent; 29import android.app.StatusBarManager; 30import android.content.BroadcastReceiver; 31import android.content.Context; 32import android.content.Intent; 33import android.content.IntentFilter; 34import android.content.SharedPreferences; 35import android.content.pm.ApplicationInfo; 36import android.content.pm.PackageManager.NameNotFoundException; 37import android.content.res.Configuration; 38import android.content.res.Resources; 39import android.inputmethodservice.InputMethodService; 40import android.graphics.PixelFormat; 41import android.graphics.Point; 42import android.graphics.Rect; 43import android.graphics.drawable.Drawable; 44import android.graphics.drawable.LayerDrawable; 45import android.os.Build; 46import android.os.Handler; 47import android.os.IBinder; 48import android.os.Message; 49import android.os.RemoteException; 50import android.os.ServiceManager; 51import android.text.TextUtils; 52import android.util.Slog; 53import android.view.accessibility.AccessibilityEvent; 54import android.view.Display; 55import android.view.Gravity; 56import android.view.IWindowManager; 57import android.view.KeyEvent; 58import android.view.LayoutInflater; 59import android.view.MotionEvent; 60import android.view.SoundEffectConstants; 61import android.view.VelocityTracker; 62import android.view.View; 63import android.view.ViewConfiguration; 64import android.view.ViewGroup; 65import android.view.WindowManager; 66import android.view.WindowManagerImpl; 67import android.widget.ImageView; 68import android.widget.LinearLayout; 69import android.widget.RemoteViews; 70import android.widget.ScrollView; 71import android.widget.TextView; 72 73import com.android.internal.statusbar.StatusBarIcon; 74import com.android.internal.statusbar.StatusBarNotification; 75import com.android.systemui.R; 76import com.android.systemui.recent.RecentTasksLoader; 77import com.android.systemui.recent.RecentsPanelView; 78import com.android.systemui.statusbar.BaseStatusBar; 79import com.android.systemui.statusbar.NotificationData; 80import com.android.systemui.statusbar.SignalClusterView; 81import com.android.systemui.statusbar.BaseStatusBar; 82import com.android.systemui.statusbar.StatusBarIconView; 83import com.android.systemui.statusbar.policy.BatteryController; 84import com.android.systemui.statusbar.policy.BluetoothController; 85import com.android.systemui.statusbar.policy.CompatModeButton; 86import com.android.systemui.statusbar.policy.LocationController; 87import com.android.systemui.statusbar.policy.NetworkController; 88import com.android.systemui.statusbar.policy.Prefs; 89 90public class TabletStatusBar extends BaseStatusBar implements 91 HeightReceiver.OnBarHeightChangedListener, 92 InputMethodsPanel.OnHardKeyboardEnabledChangeListener, 93 RecentsPanelView.OnRecentsPanelVisibilityChangedListener { 94 public static final boolean DEBUG = false; 95 public static final boolean DEBUG_COMPAT_HELP = false; 96 public static final String TAG = "TabletStatusBar"; 97 98 99 public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; 100 public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; 101 public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002; 102 public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003; 103 public static final int MSG_OPEN_RECENTS_PANEL = 1020; 104 public static final int MSG_CLOSE_RECENTS_PANEL = 1021; 105 public static final int MSG_SHOW_CHROME = 1030; 106 public static final int MSG_HIDE_CHROME = 1031; 107 public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040; 108 public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041; 109 public static final int MSG_OPEN_COMPAT_MODE_PANEL = 1050; 110 public static final int MSG_CLOSE_COMPAT_MODE_PANEL = 1051; 111 public static final int MSG_STOP_TICKER = 2000; 112 113 // Fitts' Law assistance for LatinIME; see policy.EventHole 114 private static final boolean FAKE_SPACE_BAR = true; 115 116 // Notification "peeking" (flyover preview of individual notifications) 117 final static boolean NOTIFICATION_PEEK_ENABLED = false; 118 final static int NOTIFICATION_PEEK_HOLD_THRESH = 200; // ms 119 final static int NOTIFICATION_PEEK_FADE_DELAY = 3000; // ms 120 121 // The height of the bar, as definied by the build. It may be taller if we're plugged 122 // into hdmi. 123 int mNaturalBarHeight = -1; 124 int mIconSize = -1; 125 int mIconHPadding = -1; 126 int mNavIconWidth = -1; 127 int mMenuNavIconWidth = -1; 128 private int mMaxNotificationIcons = 5; 129 130 H mHandler = new H(); 131 132 IWindowManager mWindowManager; 133 134 // tracking all current notifications 135 private NotificationData mNotificationData = new NotificationData(); 136 137 TabletStatusBarView mStatusBarView; 138 View mNotificationArea; 139 View mNotificationTrigger; 140 NotificationIconArea mNotificationIconArea; 141 ViewGroup mNavigationArea; 142 143 boolean mNotificationDNDMode; 144 NotificationData.Entry mNotificationDNDDummyEntry; 145 146 ImageView mBackButton; 147 View mHomeButton; 148 View mMenuButton; 149 View mRecentButton; 150 private boolean mAltBackButtonEnabledForIme; 151 152 ViewGroup mFeedbackIconArea; // notification icons, IME icon, compat icon 153 InputMethodButton mInputMethodSwitchButton; 154 CompatModeButton mCompatModeButton; 155 156 NotificationPanel mNotificationPanel; 157 WindowManager.LayoutParams mNotificationPanelParams; 158 NotificationPeekPanel mNotificationPeekWindow; 159 ViewGroup mNotificationPeekRow; 160 int mNotificationPeekIndex; 161 IBinder mNotificationPeekKey; 162 LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight; 163 164 int mNotificationPeekTapDuration; 165 int mNotificationFlingVelocity; 166 167 ViewGroup mPile; 168 169 HeightReceiver mHeightReceiver; 170 BatteryController mBatteryController; 171 BluetoothController mBluetoothController; 172 LocationController mLocationController; 173 NetworkController mNetworkController; 174 175 ViewGroup mBarContents; 176 177 // hide system chrome ("lights out") support 178 View mShadow; 179 180 NotificationIconArea.IconLayout mIconLayout; 181 182 TabletTicker mTicker; 183 184 View mFakeSpaceBar; 185 KeyEvent mSpaceBarKeyEvent = null; 186 187 View mCompatibilityHelpDialog = null; 188 189 // for disabling the status bar 190 int mDisabled = 0; 191 192 private RecentsPanelView mRecentsPanel; 193 private RecentTasksLoader mRecentTasksLoader; 194 private InputMethodsPanel mInputMethodsPanel; 195 private CompatModePanel mCompatModePanel; 196 197 private int mSystemUiVisibility = 0; 198 // used to notify status bar for suppressing notification LED 199 private boolean mPanelSlightlyVisible; 200 201 private int mNavigationIconHints = 0; 202 203 public Context getContext() { return mContext; } 204 205 @Override 206 protected void createAndAddWindows() { 207 addStatusBarWindow(); 208 addPanelWindows(); 209 } 210 211 private void addStatusBarWindow() { 212 final View sb = makeStatusBarView(); 213 final int height = getStatusBarHeight(); 214 215 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 216 ViewGroup.LayoutParams.MATCH_PARENT, 217 height, 218 WindowManager.LayoutParams.TYPE_STATUS_BAR, 219 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 220 | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING 221 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 222 // We use a pixel format of RGB565 for the status bar to save memory bandwidth and 223 // to ensure that the layer can be handled by HWComposer. On some devices the 224 // HWComposer is unable to handle SW-rendered RGBX_8888 layers. 225 PixelFormat.RGB_565); 226 227 // the status bar should be in an overlay if possible 228 final Display defaultDisplay 229 = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) 230 .getDefaultDisplay(); 231 232 // We explicitly leave FLAG_HARDWARE_ACCELERATED out of the flags. The status bar occupies 233 // very little screen real-estate and is updated fairly frequently. By using CPU rendering 234 // for the status bar, we prevent the GPU from having to wake up just to do these small 235 // updates, which should help keep power consumption down. 236 237 lp.gravity = getStatusBarGravity(); 238 lp.setTitle("StatusBar"); 239 lp.packageName = mContext.getPackageName(); 240 lp.windowAnimations = R.style.Animation_StatusBar; 241 WindowManagerImpl.getDefault().addView(sb, lp); 242 } 243 244 protected void addPanelWindows() { 245 final Context context = mContext; 246 final Resources res = mContext.getResources(); 247 248 // Notification Panel 249 mNotificationPanel = (NotificationPanel)View.inflate(context, 250 R.layout.status_bar_notification_panel, null); 251 mNotificationPanel.setBar(this); 252 mNotificationPanel.show(false, false); 253 mNotificationPanel.setOnTouchListener( 254 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel)); 255 256 // the battery icon 257 mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery)); 258 mBatteryController.addLabelView( 259 (TextView)mNotificationPanel.findViewById(R.id.battery_text)); 260 261 // Bt 262 mBluetoothController.addIconView( 263 (ImageView)mNotificationPanel.findViewById(R.id.bluetooth)); 264 265 // network icons: either a combo icon that switches between mobile and data, or distinct 266 // mobile and data icons 267 final ImageView mobileRSSI = 268 (ImageView)mNotificationPanel.findViewById(R.id.mobile_signal); 269 if (mobileRSSI != null) { 270 mNetworkController.addPhoneSignalIconView(mobileRSSI); 271 } 272 final ImageView wifiRSSI = 273 (ImageView)mNotificationPanel.findViewById(R.id.wifi_signal); 274 if (wifiRSSI != null) { 275 mNetworkController.addWifiIconView(wifiRSSI); 276 } 277 mNetworkController.addWifiLabelView( 278 (TextView)mNotificationPanel.findViewById(R.id.wifi_text)); 279 280 mNetworkController.addDataTypeIconView( 281 (ImageView)mNotificationPanel.findViewById(R.id.mobile_type)); 282 mNetworkController.addMobileLabelView( 283 (TextView)mNotificationPanel.findViewById(R.id.mobile_text)); 284 mNetworkController.addCombinedLabelView( 285 (TextView)mBarContents.findViewById(R.id.network_text)); 286 287 mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel); 288 289 WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams( 290 res.getDimensionPixelSize(R.dimen.notification_panel_width), 291 getNotificationPanelHeight(), 292 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 293 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 294 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 295 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 296 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 297 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 298 PixelFormat.TRANSLUCENT); 299 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 300 lp.setTitle("NotificationPanel"); 301 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 302 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 303 lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation 304// lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade 305 306 WindowManagerImpl.getDefault().addView(mNotificationPanel, lp); 307 308 // Notification preview window 309 if (NOTIFICATION_PEEK_ENABLED) { 310 mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context, 311 R.layout.status_bar_notification_peek, null); 312 mNotificationPeekWindow.setBar(this); 313 314 mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content); 315 mNotificationPeekWindow.setVisibility(View.GONE); 316 mNotificationPeekWindow.setOnTouchListener( 317 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow)); 318 mNotificationPeekScrubRight = new LayoutTransition(); 319 mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, 320 ObjectAnimator.ofInt(null, "left", -512, 0)); 321 mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, 322 ObjectAnimator.ofInt(null, "left", -512, 0)); 323 mNotificationPeekScrubRight.setDuration(500); 324 325 mNotificationPeekScrubLeft = new LayoutTransition(); 326 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, 327 ObjectAnimator.ofInt(null, "left", 512, 0)); 328 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, 329 ObjectAnimator.ofInt(null, "left", 512, 0)); 330 mNotificationPeekScrubLeft.setDuration(500); 331 332 // XXX: setIgnoreChildren? 333 lp = new WindowManager.LayoutParams( 334 512, // ViewGroup.LayoutParams.WRAP_CONTENT, 335 ViewGroup.LayoutParams.WRAP_CONTENT, 336 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 337 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 338 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 339 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 340 PixelFormat.TRANSLUCENT); 341 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 342 lp.y = res.getDimensionPixelOffset(R.dimen.peek_window_y_offset); 343 lp.setTitle("NotificationPeekWindow"); 344 lp.windowAnimations = com.android.internal.R.style.Animation_Toast; 345 346 WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp); 347 } 348 349 // Recents Panel 350 mRecentTasksLoader = new RecentTasksLoader(context); 351 mRecentsPanel = (RecentsPanelView) View.inflate(context, 352 R.layout.status_bar_recent_panel, null); 353 mRecentsPanel.setVisibility(View.GONE); 354 mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, 355 mRecentsPanel)); 356 mRecentsPanel.setOnVisibilityChangedListener(this); 357 mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader); 358 mRecentTasksLoader.setRecentsPanel(mRecentsPanel); 359 360 lp = new WindowManager.LayoutParams( 361 (int) res.getDimension(R.dimen.status_bar_recents_width), 362 ViewGroup.LayoutParams.MATCH_PARENT, 363 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 364 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 365 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 366 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 367 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 368 PixelFormat.TRANSLUCENT); 369 lp.gravity = Gravity.BOTTOM | Gravity.LEFT; 370 lp.setTitle("RecentsPanel"); 371 lp.windowAnimations = R.style.Animation_RecentPanel; 372 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 373 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 374 375 WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); 376 mRecentsPanel.setBar(this); 377 mRecentsPanel.setStatusBarView(mStatusBarView); 378 379 // Input methods Panel 380 mInputMethodsPanel = (InputMethodsPanel) View.inflate(context, 381 R.layout.status_bar_input_methods_panel, null); 382 mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this); 383 mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener( 384 MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel)); 385 mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton); 386 mStatusBarView.setIgnoreChildren(2, mInputMethodSwitchButton, mInputMethodsPanel); 387 lp = new WindowManager.LayoutParams( 388 ViewGroup.LayoutParams.WRAP_CONTENT, 389 ViewGroup.LayoutParams.WRAP_CONTENT, 390 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 391 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 392 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 393 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 394 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 395 PixelFormat.TRANSLUCENT); 396 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 397 lp.setTitle("InputMethodsPanel"); 398 lp.windowAnimations = R.style.Animation_RecentPanel; 399 400 WindowManagerImpl.getDefault().addView(mInputMethodsPanel, lp); 401 402 // Compatibility mode selector panel 403 mCompatModePanel = (CompatModePanel) View.inflate(context, 404 R.layout.status_bar_compat_mode_panel, null); 405 mCompatModePanel.setOnTouchListener(new TouchOutsideListener( 406 MSG_CLOSE_COMPAT_MODE_PANEL, mCompatModePanel)); 407 mCompatModePanel.setTrigger(mCompatModeButton); 408 mCompatModePanel.setVisibility(View.GONE); 409 mStatusBarView.setIgnoreChildren(3, mCompatModeButton, mCompatModePanel); 410 lp = new WindowManager.LayoutParams( 411 250, 412 ViewGroup.LayoutParams.WRAP_CONTENT, 413 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 414 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 415 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 416 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 417 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 418 PixelFormat.TRANSLUCENT); 419 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 420 lp.setTitle("CompatModePanel"); 421 lp.windowAnimations = android.R.style.Animation_Dialog; 422 423 WindowManagerImpl.getDefault().addView(mCompatModePanel, lp); 424 425 mRecentButton.setOnTouchListener(mRecentsPanel); 426 427 mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content); 428 mPile.removeAllViews(); 429 430 ScrollView scroller = (ScrollView)mPile.getParent(); 431 scroller.setFillViewport(true); 432 } 433 434 private int getNotificationPanelHeight() { 435 final Resources res = mContext.getResources(); 436 final Display d = WindowManagerImpl.getDefault().getDefaultDisplay(); 437 final Point size = new Point(); 438 d.getRealSize(size); 439 return Math.max(res.getDimensionPixelSize(R.dimen.notification_panel_min_height), size.y); 440 } 441 442 @Override 443 public void start() { 444 super.start(); // will add the main bar view 445 } 446 447 @Override 448 protected void onConfigurationChanged(Configuration newConfig) { 449 mHeightReceiver.updateHeight(); // display size may have changed 450 loadDimens(); 451 mNotificationPanelParams.height = getNotificationPanelHeight(); 452 WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel, 453 mNotificationPanelParams); 454 mRecentsPanel.updateValuesFromResources(); 455 } 456 457 protected void loadDimens() { 458 final Resources res = mContext.getResources(); 459 460 mNaturalBarHeight = res.getDimensionPixelSize( 461 com.android.internal.R.dimen.system_bar_height); 462 463 int newIconSize = res.getDimensionPixelSize( 464 com.android.internal.R.dimen.system_bar_icon_size); 465 int newIconHPadding = res.getDimensionPixelSize( 466 R.dimen.status_bar_icon_padding); 467 int newNavIconWidth = res.getDimensionPixelSize(R.dimen.navigation_key_width); 468 int newMenuNavIconWidth = res.getDimensionPixelSize(R.dimen.navigation_menu_key_width); 469 470 if (mNavigationArea != null && newNavIconWidth != mNavIconWidth) { 471 mNavIconWidth = newNavIconWidth; 472 473 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 474 mNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT); 475 mBackButton.setLayoutParams(lp); 476 mHomeButton.setLayoutParams(lp); 477 mRecentButton.setLayoutParams(lp); 478 } 479 480 if (mNavigationArea != null && newMenuNavIconWidth != mMenuNavIconWidth) { 481 mMenuNavIconWidth = newMenuNavIconWidth; 482 483 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 484 mMenuNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT); 485 mMenuButton.setLayoutParams(lp); 486 } 487 488 if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { 489// Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); 490 mIconHPadding = newIconHPadding; 491 mIconSize = newIconSize; 492 reloadAllNotificationIcons(); // reload the tray 493 } 494 495 final int numIcons = res.getInteger(R.integer.config_maxNotificationIcons); 496 if (numIcons != mMaxNotificationIcons) { 497 mMaxNotificationIcons = numIcons; 498 if (DEBUG) Slog.d(TAG, "max notification icons: " + mMaxNotificationIcons); 499 reloadAllNotificationIcons(); 500 } 501 } 502 503 public View getStatusBarView() { 504 return mStatusBarView; 505 } 506 507 protected View makeStatusBarView() { 508 final Context context = mContext; 509 510 mWindowManager = IWindowManager.Stub.asInterface( 511 ServiceManager.getService(Context.WINDOW_SERVICE)); 512 513 // This guy will listen for HDMI plugged broadcasts so we can resize the 514 // status bar as appropriate. 515 mHeightReceiver = new HeightReceiver(mContext); 516 mHeightReceiver.registerReceiver(); 517 loadDimens(); 518 519 final TabletStatusBarView sb = (TabletStatusBarView)View.inflate( 520 context, R.layout.status_bar, null); 521 mStatusBarView = sb; 522 523 sb.setHandler(mHandler); 524 525 try { 526 // Sanity-check that someone hasn't set up the config wrong and asked for a navigation 527 // bar on a tablet that has only the system bar 528 if (mWindowManager.hasNavigationBar()) { 529 Slog.e(TAG, "Tablet device cannot show navigation bar and system bar"); 530 } 531 } catch (RemoteException ex) { 532 } 533 534 mBarContents = (ViewGroup) sb.findViewById(R.id.bar_contents); 535 536 // the whole right-hand side of the bar 537 mNotificationArea = sb.findViewById(R.id.notificationArea); 538 if (!NOTIFICATION_PEEK_ENABLED) { 539 mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener()); 540 } 541 542 // the button to open the notification area 543 mNotificationTrigger = sb.findViewById(R.id.notificationTrigger); 544 if (NOTIFICATION_PEEK_ENABLED) { 545 mNotificationTrigger.setOnTouchListener(new NotificationTriggerTouchListener()); 546 } 547 548 // the more notifications icon 549 mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons); 550 551 // where the icons go 552 mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons); 553 if (NOTIFICATION_PEEK_ENABLED) { 554 mIconLayout.setOnTouchListener(new NotificationIconTouchListener()); 555 } 556 557 ViewConfiguration vc = ViewConfiguration.get(context); 558 mNotificationPeekTapDuration = vc.getTapTimeout(); 559 mNotificationFlingVelocity = 300; // px/s 560 561 mTicker = new TabletTicker(this); 562 563 // The icons 564 mLocationController = new LocationController(mContext); // will post a notification 565 566 mBatteryController = new BatteryController(mContext); 567 mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); 568 mBluetoothController = new BluetoothController(mContext); 569 mBluetoothController.addIconView((ImageView)sb.findViewById(R.id.bluetooth)); 570 571 mNetworkController = new NetworkController(mContext); 572 final SignalClusterView signalCluster = 573 (SignalClusterView)sb.findViewById(R.id.signal_cluster); 574 mNetworkController.addSignalCluster(signalCluster); 575 576 // The navigation buttons 577 mBackButton = (ImageView)sb.findViewById(R.id.back); 578 mNavigationArea = (ViewGroup) sb.findViewById(R.id.navigationArea); 579 mHomeButton = mNavigationArea.findViewById(R.id.home); 580 mMenuButton = mNavigationArea.findViewById(R.id.menu); 581 mRecentButton = mNavigationArea.findViewById(R.id.recent_apps); 582 mRecentButton.setOnClickListener(mOnClickListener); 583 584 LayoutTransition lt = new LayoutTransition(); 585 lt.setDuration(250); 586 // don't wait for these transitions; we just want icons to fade in/out, not move around 587 lt.setDuration(LayoutTransition.CHANGE_APPEARING, 0); 588 lt.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 0); 589 lt.addTransitionListener(new LayoutTransition.TransitionListener() { 590 public void endTransition(LayoutTransition transition, ViewGroup container, 591 View view, int transitionType) { 592 // ensure the menu button doesn't stick around on the status bar after it's been 593 // removed 594 mBarContents.invalidate(); 595 } 596 public void startTransition(LayoutTransition transition, ViewGroup container, 597 View view, int transitionType) {} 598 }); 599 mNavigationArea.setLayoutTransition(lt); 600 // no multi-touch on the nav buttons 601 mNavigationArea.setMotionEventSplittingEnabled(false); 602 603 // The bar contents buttons 604 mFeedbackIconArea = (ViewGroup)sb.findViewById(R.id.feedbackIconArea); 605 mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton); 606 // Overwrite the lister 607 mInputMethodSwitchButton.setOnClickListener(mOnClickListener); 608 609 mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton); 610 mCompatModeButton.setOnClickListener(mOnClickListener); 611 mCompatModeButton.setVisibility(View.GONE); 612 613 // for redirecting errant bar taps to the IME 614 mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar); 615 616 // "shadows" of the status bar features, for lights-out mode 617 mShadow = sb.findViewById(R.id.bar_shadow); 618 mShadow.setOnTouchListener( 619 new View.OnTouchListener() { 620 public boolean onTouch(View v, MotionEvent ev) { 621 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 622 // even though setting the systemUI visibility below will turn these views 623 // on, we need them to come up faster so that they can catch this motion 624 // event 625 mShadow.setVisibility(View.GONE); 626 mBarContents.setVisibility(View.VISIBLE); 627 628 try { 629 mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE); 630 } catch (RemoteException ex) { 631 // system process dead 632 } 633 } 634 return false; 635 } 636 }); 637 638 // tuning parameters 639 final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 600; 640 final int LIGHTS_GOING_OUT_SHADOW_DURATION = 1000; 641 final int LIGHTS_GOING_OUT_SHADOW_DELAY = 500; 642 643 final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200; 644// final int LIGHTS_COMING_UP_SYSBAR_DELAY = 50; 645 final int LIGHTS_COMING_UP_SHADOW_DURATION = 0; 646 647 LayoutTransition xition = new LayoutTransition(); 648 xition.setAnimator(LayoutTransition.APPEARING, 649 ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)); 650 xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION); 651 xition.setStartDelay(LayoutTransition.APPEARING, 0); 652 xition.setAnimator(LayoutTransition.DISAPPEARING, 653 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 654 xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION); 655 xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 656 ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition); 657 658 xition = new LayoutTransition(); 659 xition.setAnimator(LayoutTransition.APPEARING, 660 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); 661 xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION); 662 xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY); 663 xition.setAnimator(LayoutTransition.DISAPPEARING, 664 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 665 xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION); 666 xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 667 ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition); 668 669 // set the initial view visibility 670 setAreThereNotifications(); 671 672 mHeightReceiver.addOnBarHeightChangedListener(this); 673 674 // receive broadcasts 675 IntentFilter filter = new IntentFilter(); 676 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 677 filter.addAction(Intent.ACTION_SCREEN_OFF); 678 context.registerReceiver(mBroadcastReceiver, filter); 679 680 return sb; 681 } 682 683 public int getStatusBarHeight() { 684 return mHeightReceiver.getHeight(); 685 } 686 687 protected int getStatusBarGravity() { 688 return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL; 689 } 690 691 public void onBarHeightChanged(int height) { 692 final WindowManager.LayoutParams lp 693 = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams(); 694 if (lp == null) { 695 // haven't been added yet 696 return; 697 } 698 if (lp.height != height) { 699 lp.height = height; 700 final WindowManager wm = WindowManagerImpl.getDefault(); 701 wm.updateViewLayout(mStatusBarView, lp); 702 } 703 } 704 705 private class H extends Handler { 706 public void handleMessage(Message m) { 707 switch (m.what) { 708 case MSG_OPEN_NOTIFICATION_PEEK: 709 if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1); 710 711 if (m.arg1 >= 0) { 712 final int N = mNotificationData.size(); 713 714 if (!mNotificationDNDMode) { 715 if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { 716 NotificationData.Entry entry = mNotificationData.get(N-1-mNotificationPeekIndex); 717 entry.icon.setBackgroundColor(0); 718 mNotificationPeekIndex = -1; 719 mNotificationPeekKey = null; 720 } 721 } 722 723 final int peekIndex = m.arg1; 724 if (peekIndex < N) { 725 //Slog.d(TAG, "loading peek: " + peekIndex); 726 NotificationData.Entry entry = 727 mNotificationDNDMode 728 ? mNotificationDNDDummyEntry 729 : mNotificationData.get(N-1-peekIndex); 730 NotificationData.Entry copy = new NotificationData.Entry( 731 entry.key, 732 entry.notification, 733 entry.icon); 734 inflateViews(copy, mNotificationPeekRow); 735 736 if (mNotificationDNDMode) { 737 copy.content.setOnClickListener(new View.OnClickListener() { 738 public void onClick(View v) { 739 SharedPreferences.Editor editor = Prefs.edit(mContext); 740 editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false); 741 editor.apply(); 742 animateCollapse(); 743 visibilityChanged(false); 744 } 745 }); 746 } 747 748 entry.icon.setBackgroundColor(0x20FFFFFF); 749 750// mNotificationPeekRow.setLayoutTransition( 751// peekIndex < mNotificationPeekIndex 752// ? mNotificationPeekScrubLeft 753// : mNotificationPeekScrubRight); 754 755 mNotificationPeekRow.removeAllViews(); 756 mNotificationPeekRow.addView(copy.row); 757 758 mNotificationPeekWindow.setVisibility(View.VISIBLE); 759 mNotificationPanel.show(false, true); 760 761 mNotificationPeekIndex = peekIndex; 762 mNotificationPeekKey = entry.key; 763 } 764 } 765 break; 766 case MSG_CLOSE_NOTIFICATION_PEEK: 767 if (DEBUG) Slog.d(TAG, "closing notification peek window"); 768 mNotificationPeekWindow.setVisibility(View.GONE); 769 mNotificationPeekRow.removeAllViews(); 770 771 final int N = mNotificationData.size(); 772 if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { 773 NotificationData.Entry entry = 774 mNotificationDNDMode 775 ? mNotificationDNDDummyEntry 776 : mNotificationData.get(N-1-mNotificationPeekIndex); 777 entry.icon.setBackgroundColor(0); 778 } 779 780 mNotificationPeekIndex = -1; 781 mNotificationPeekKey = null; 782 break; 783 case MSG_OPEN_NOTIFICATION_PANEL: 784 if (DEBUG) Slog.d(TAG, "opening notifications panel"); 785 if (!mNotificationPanel.isShowing()) { 786 if (NOTIFICATION_PEEK_ENABLED) { 787 mNotificationPeekWindow.setVisibility(View.GONE); 788 } 789 mNotificationPanel.show(true, true); 790 mNotificationArea.setVisibility(View.INVISIBLE); 791 mTicker.halt(); 792 } 793 break; 794 case MSG_CLOSE_NOTIFICATION_PANEL: 795 if (DEBUG) Slog.d(TAG, "closing notifications panel"); 796 if (mNotificationPanel.isShowing()) { 797 mNotificationPanel.show(false, true); 798 mNotificationArea.setVisibility(View.VISIBLE); 799 } 800 break; 801 case MSG_OPEN_RECENTS_PANEL: 802 if (DEBUG) Slog.d(TAG, "opening recents panel"); 803 if (mRecentsPanel != null) { 804 mRecentsPanel.show(true, true); 805 } 806 break; 807 case MSG_CLOSE_RECENTS_PANEL: 808 if (DEBUG) Slog.d(TAG, "closing recents panel"); 809 if (mRecentsPanel != null && mRecentsPanel.isShowing()) { 810 mRecentsPanel.show(false, true); 811 } 812 break; 813 case MSG_OPEN_INPUT_METHODS_PANEL: 814 if (DEBUG) Slog.d(TAG, "opening input methods panel"); 815 if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel(); 816 break; 817 case MSG_CLOSE_INPUT_METHODS_PANEL: 818 if (DEBUG) Slog.d(TAG, "closing input methods panel"); 819 if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false); 820 break; 821 case MSG_OPEN_COMPAT_MODE_PANEL: 822 if (DEBUG) Slog.d(TAG, "opening compat panel"); 823 if (mCompatModePanel != null) mCompatModePanel.openPanel(); 824 break; 825 case MSG_CLOSE_COMPAT_MODE_PANEL: 826 if (DEBUG) Slog.d(TAG, "closing compat panel"); 827 if (mCompatModePanel != null) mCompatModePanel.closePanel(); 828 break; 829 case MSG_SHOW_CHROME: 830 if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)"); 831 mBarContents.setVisibility(View.VISIBLE); 832 mShadow.setVisibility(View.GONE); 833 mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; 834 notifyUiVisibilityChanged(); 835 break; 836 case MSG_HIDE_CHROME: 837 if (DEBUG) Slog.d(TAG, "showing shadows (lights out)"); 838 animateCollapse(); 839 visibilityChanged(false); 840 mBarContents.setVisibility(View.GONE); 841 mShadow.setVisibility(View.VISIBLE); 842 mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE; 843 notifyUiVisibilityChanged(); 844 break; 845 case MSG_STOP_TICKER: 846 mTicker.halt(); 847 break; 848 } 849 } 850 } 851 852 public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 853 if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon); 854 } 855 856 public void updateIcon(String slot, int index, int viewIndex, 857 StatusBarIcon old, StatusBarIcon icon) { 858 if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon); 859 } 860 861 public void removeIcon(String slot, int index, int viewIndex) { 862 if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")"); 863 } 864 865 public void addNotification(IBinder key, StatusBarNotification notification) { 866 if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); 867 addNotificationViews(key, notification); 868 869 final boolean immersive = isImmersive(); 870 if (false && immersive) { 871 // TODO: immersive mode popups for tablet 872 } else if (notification.notification.fullScreenIntent != null) { 873 // not immersive & a full-screen alert should be shown 874 Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;" 875 + " sending fullScreenIntent"); 876 try { 877 notification.notification.fullScreenIntent.send(); 878 } catch (PendingIntent.CanceledException e) { 879 } 880 } else { 881 tick(key, notification, true); 882 } 883 884 setAreThereNotifications(); 885 } 886 887 public void updateNotification(IBinder key, StatusBarNotification notification) { 888 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 889 890 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 891 if (oldEntry == null) { 892 Slog.w(TAG, "updateNotification for unknown key: " + key); 893 return; 894 } 895 896 final StatusBarNotification oldNotification = oldEntry.notification; 897 final RemoteViews oldContentView = oldNotification.notification.contentView; 898 899 final RemoteViews contentView = notification.notification.contentView; 900 901 if (DEBUG) { 902 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 903 + " ongoing=" + oldNotification.isOngoing() 904 + " expanded=" + oldEntry.expanded 905 + " contentView=" + oldContentView 906 + " rowParent=" + oldEntry.row.getParent()); 907 Slog.d(TAG, "new notification: when=" + notification.notification.when 908 + " ongoing=" + oldNotification.isOngoing() 909 + " contentView=" + contentView); 910 } 911 912 // Can we just reapply the RemoteViews in place? If when didn't change, the order 913 // didn't change. 914 boolean contentsUnchanged = oldEntry.expanded != null 915 && contentView != null && oldContentView != null 916 && contentView.getPackage() != null 917 && oldContentView.getPackage() != null 918 && oldContentView.getPackage().equals(contentView.getPackage()) 919 && oldContentView.getLayoutId() == contentView.getLayoutId(); 920 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 921 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 922 && notification.score == oldNotification.score; 923 // score now encompasses/supersedes isOngoing() 924 boolean updateTicker = notification.notification.tickerText != null 925 && !TextUtils.equals(notification.notification.tickerText, 926 oldEntry.notification.notification.tickerText); 927 boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1; 928 if (contentsUnchanged && (orderUnchanged || isLastAnyway)) { 929 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 930 oldEntry.notification = notification; 931 try { 932 // Reapply the RemoteViews 933 contentView.reapply(mContext, oldEntry.content); 934 // update the contentIntent 935 final PendingIntent contentIntent = notification.notification.contentIntent; 936 if (contentIntent != null) { 937 final View.OnClickListener listener = new NotificationClicker(contentIntent, 938 notification.pkg, notification.tag, notification.id); 939 oldEntry.content.setOnClickListener(listener); 940 } else { 941 oldEntry.content.setOnClickListener(null); 942 } 943 // Update the icon. 944 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 945 notification.notification.icon, notification.notification.iconLevel, 946 notification.notification.number, 947 notification.notification.tickerText); 948 if (!oldEntry.icon.set(ic)) { 949 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 950 return; 951 } 952 953 if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { 954 // must update the peek window 955 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 956 peekMsg.arg1 = mNotificationPeekIndex; 957 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 958 mHandler.sendMessage(peekMsg); 959 } 960 } 961 catch (RuntimeException e) { 962 // It failed to add cleanly. Log, and remove the view from the panel. 963 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 964 removeNotificationViews(key); 965 addNotificationViews(key, notification); 966 } 967 } else { 968 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 969 removeNotificationViews(key); 970 addNotificationViews(key, notification); 971 } 972 973 // Restart the ticker if it's still running 974 if (updateTicker) { 975 mTicker.halt(); 976 tick(key, notification, false); 977 } 978 979 setAreThereNotifications(); 980 } 981 982 public void removeNotification(IBinder key) { 983 if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ")"); 984 removeNotificationViews(key); 985 mTicker.remove(key); 986 setAreThereNotifications(); 987 } 988 989 public void showClock(boolean show) { 990 View clock = mBarContents.findViewById(R.id.clock); 991 View network_text = mBarContents.findViewById(R.id.network_text); 992 if (clock != null) { 993 clock.setVisibility(show ? View.VISIBLE : View.GONE); 994 } 995 if (network_text != null) { 996 network_text.setVisibility((!show) ? View.VISIBLE : View.GONE); 997 } 998 } 999 1000 public void disable(int state) { 1001 int old = mDisabled; 1002 int diff = state ^ old; 1003 mDisabled = state; 1004 1005 // act accordingly 1006 if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { 1007 boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; 1008 Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes")); 1009 showClock(show); 1010 } 1011 if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { 1012 boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0; 1013 Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes")); 1014 mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE); 1015 } 1016 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 1017 if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { 1018 Slog.i(TAG, "DISABLE_EXPAND: yes"); 1019 animateCollapse(); 1020 visibilityChanged(false); 1021 } 1022 } 1023 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 1024 mNotificationDNDMode = Prefs.read(mContext) 1025 .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT); 1026 1027 if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 1028 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":"")); 1029 mTicker.halt(); 1030 } else { 1031 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":"")); 1032 } 1033 1034 // refresh icons to show either notifications or the DND message 1035 reloadAllNotificationIcons(); 1036 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 1037 if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 1038 mTicker.halt(); 1039 } 1040 } 1041 if ((diff & (StatusBarManager.DISABLE_RECENT 1042 | StatusBarManager.DISABLE_BACK 1043 | StatusBarManager.DISABLE_HOME)) != 0) { 1044 setNavigationVisibility(state); 1045 1046 if ((state & StatusBarManager.DISABLE_RECENT) != 0) { 1047 // close recents if it's visible 1048 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 1049 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 1050 } 1051 } 1052 } 1053 1054 private void setNavigationVisibility(int visibility) { 1055 boolean disableHome = ((visibility & StatusBarManager.DISABLE_HOME) != 0); 1056 boolean disableRecent = ((visibility & StatusBarManager.DISABLE_RECENT) != 0); 1057 boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0); 1058 1059 mBackButton.setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); 1060 mHomeButton.setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); 1061 mRecentButton.setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); 1062 1063 mInputMethodSwitchButton.setScreenLocked( 1064 (visibility & StatusBarManager.DISABLE_SYSTEM_INFO) != 0); 1065 } 1066 1067 private boolean hasTicker(Notification n) { 1068 return n.tickerView != null || !TextUtils.isEmpty(n.tickerText); 1069 } 1070 1071 private void tick(IBinder key, StatusBarNotification n, boolean firstTime) { 1072 // Don't show the ticker when the windowshade is open. 1073 if (mNotificationPanel.isShowing()) { 1074 return; 1075 } 1076 // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification 1077 // if it's a new notification. 1078 if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) { 1079 return; 1080 } 1081 // Show the ticker if one is requested. Also don't do this 1082 // until status bar window is attached to the window manager, 1083 // because... well, what's the point otherwise? And trying to 1084 // run a ticker without being attached will crash! 1085 if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) { 1086 if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS 1087 | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { 1088 mTicker.add(key, n); 1089 mFeedbackIconArea.setVisibility(View.GONE); 1090 } 1091 } 1092 } 1093 1094 // called by TabletTicker when it's done with all queued ticks 1095 public void doneTicking() { 1096 mFeedbackIconArea.setVisibility(View.VISIBLE); 1097 } 1098 1099 public void animateExpand() { 1100 if (NOTIFICATION_PEEK_ENABLED) { 1101 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1102 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1103 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1104 } 1105 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 1106 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 1107 } 1108 1109 public void animateCollapse() { 1110 animateCollapse(false); 1111 } 1112 1113 private void animateCollapse(boolean excludeRecents) { 1114 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); 1115 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); 1116 if (!excludeRecents) { 1117 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 1118 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 1119 } 1120 mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL); 1121 mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL); 1122 mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL); 1123 mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL); 1124 if (NOTIFICATION_PEEK_ENABLED) { 1125 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1126 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1127 } 1128 } 1129 1130 /** 1131 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 1132 * This was added last-minute and is inconsistent with the way the rest of the notifications 1133 * are handled, because the notification isn't really cancelled. The lights are just 1134 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1135 * this is what he wants. (see bug 1131461) 1136 */ 1137 void visibilityChanged(boolean visible) { 1138 if (mPanelSlightlyVisible != visible) { 1139 mPanelSlightlyVisible = visible; 1140 try { 1141 mBarService.onPanelRevealed(); 1142 } catch (RemoteException ex) { 1143 // Won't fail unless the world has ended. 1144 } 1145 } 1146 } 1147 1148 @Override // CommandQueue 1149 public void setNavigationIconHints(int hints) { 1150 if (hints == mNavigationIconHints) return; 1151 1152 if (DEBUG) { 1153 android.widget.Toast.makeText(mContext, 1154 "Navigation icon hints = " + hints, 1155 500).show(); 1156 } 1157 1158 mNavigationIconHints = hints; 1159 1160 mBackButton.setAlpha( 1161 (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f); 1162 mHomeButton.setAlpha( 1163 (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f); 1164 mRecentButton.setAlpha( 1165 (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f); 1166 1167 mBackButton.setImageResource( 1168 (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT)) 1169 ? R.drawable.ic_sysbar_back_ime 1170 : R.drawable.ic_sysbar_back); 1171 } 1172 1173 private void notifyUiVisibilityChanged() { 1174 try { 1175 mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility); 1176 } catch (RemoteException ex) { 1177 } 1178 } 1179 1180 @Override // CommandQueue 1181 public void setSystemUiVisibility(int vis) { 1182 if (vis != mSystemUiVisibility) { 1183 mSystemUiVisibility = vis; 1184 1185 mHandler.removeMessages(MSG_HIDE_CHROME); 1186 mHandler.removeMessages(MSG_SHOW_CHROME); 1187 mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) 1188 ? MSG_SHOW_CHROME : MSG_HIDE_CHROME); 1189 1190 notifyUiVisibilityChanged(); 1191 } 1192 } 1193 1194 public void setLightsOn(boolean on) { 1195 // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app 1196 // that can't handle lights-out mode. 1197 if (mMenuButton.getVisibility() == View.VISIBLE) { 1198 on = true; 1199 } 1200 1201 Slog.v(TAG, "setLightsOn(" + on + ")"); 1202 if (on) { 1203 setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE); 1204 } else { 1205 setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE); 1206 } 1207 } 1208 1209 public void topAppWindowChanged(boolean showMenu) { 1210 if (DEBUG) { 1211 Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); 1212 } 1213 mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE); 1214 1215 // See above re: lights-out policy for legacy apps. 1216 if (showMenu) setLightsOn(true); 1217 1218 mCompatModeButton.refresh(); 1219 if (mCompatModeButton.getVisibility() == View.VISIBLE) { 1220 if (DEBUG_COMPAT_HELP 1221 || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) { 1222 showCompatibilityHelp(); 1223 } 1224 } else { 1225 hideCompatibilityHelp(); 1226 mCompatModePanel.closePanel(); 1227 } 1228 } 1229 1230 private void showCompatibilityHelp() { 1231 if (mCompatibilityHelpDialog != null) { 1232 return; 1233 } 1234 1235 mCompatibilityHelpDialog = View.inflate(mContext, R.layout.compat_mode_help, null); 1236 View button = mCompatibilityHelpDialog.findViewById(R.id.button); 1237 1238 button.setOnClickListener(new View.OnClickListener() { 1239 @Override 1240 public void onClick(View v) { 1241 hideCompatibilityHelp(); 1242 SharedPreferences.Editor editor = Prefs.edit(mContext); 1243 editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true); 1244 editor.apply(); 1245 } 1246 }); 1247 1248 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 1249 ViewGroup.LayoutParams.MATCH_PARENT, 1250 ViewGroup.LayoutParams.MATCH_PARENT, 1251 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG, 1252 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1253 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 1254 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 1255 PixelFormat.TRANSLUCENT); 1256 lp.setTitle("CompatibilityModeDialog"); 1257 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 1258 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 1259 lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade 1260 1261 WindowManagerImpl.getDefault().addView(mCompatibilityHelpDialog, lp); 1262 } 1263 1264 private void hideCompatibilityHelp() { 1265 if (mCompatibilityHelpDialog != null) { 1266 WindowManagerImpl.getDefault().removeView(mCompatibilityHelpDialog); 1267 mCompatibilityHelpDialog = null; 1268 } 1269 } 1270 1271 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { 1272 mInputMethodSwitchButton.setImeWindowStatus(token, 1273 (vis & InputMethodService.IME_ACTIVE) != 0); 1274 updateNotificationIcons(); 1275 mInputMethodsPanel.setImeToken(token); 1276 1277 boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) 1278 || ((vis & InputMethodService.IME_VISIBLE) != 0); 1279 mAltBackButtonEnabledForIme = altBack; 1280 1281 mCommandQueue.setNavigationIconHints( 1282 altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT) 1283 : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT)); 1284 1285 if (FAKE_SPACE_BAR) { 1286 mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0) 1287 ? View.VISIBLE : View.GONE); 1288 } 1289 } 1290 1291 @Override 1292 public void onRecentsPanelVisibilityChanged(boolean visible) { 1293 boolean altBack = visible || mAltBackButtonEnabledForIme; 1294 mCommandQueue.setNavigationIconHints( 1295 altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT) 1296 : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT)); 1297 } 1298 1299 @Override 1300 public void setHardKeyboardStatus(boolean available, boolean enabled) { 1301 if (DEBUG) { 1302 Slog.d(TAG, "Set hard keyboard status: available=" + available 1303 + ", enabled=" + enabled); 1304 } 1305 mInputMethodSwitchButton.setHardKeyboardStatus(available); 1306 updateNotificationIcons(); 1307 mInputMethodsPanel.setHardKeyboardStatus(available, enabled); 1308 } 1309 1310 @Override 1311 public void onHardKeyboardEnabledChange(boolean enabled) { 1312 try { 1313 mBarService.setHardKeyboardEnabled(enabled); 1314 } catch (RemoteException ex) { 1315 } 1316 } 1317 1318 private boolean isImmersive() { 1319 try { 1320 return ActivityManagerNative.getDefault().isTopActivityImmersive(); 1321 //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 1322 } catch (RemoteException ex) { 1323 // the end is nigh 1324 return false; 1325 } 1326 } 1327 1328 private void setAreThereNotifications() { 1329 if (mNotificationPanel != null) { 1330 mNotificationPanel.setClearable(mNotificationData.hasClearableItems()); 1331 } 1332 } 1333 1334 /** 1335 * Cancel this notification and tell the status bar service about the failure. Hold no locks. 1336 */ 1337 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 1338 removeNotification(key); 1339 try { 1340 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 1341 } catch (RemoteException ex) { 1342 // The end is nigh. 1343 } 1344 } 1345 1346 private void sendKey(KeyEvent key) { 1347 try { 1348 if (DEBUG) Slog.d(TAG, "injecting key event: " + key); 1349 mWindowManager.injectInputEventNoWait(key); 1350 } catch (RemoteException ex) { 1351 } 1352 } 1353 1354 private View.OnClickListener mOnClickListener = new View.OnClickListener() { 1355 public void onClick(View v) { 1356 if (v == mRecentButton) { 1357 onClickRecentButton(); 1358 } else if (v == mInputMethodSwitchButton) { 1359 onClickInputMethodSwitchButton(); 1360 } else if (v == mCompatModeButton) { 1361 onClickCompatModeButton(); 1362 } 1363 } 1364 }; 1365 1366 public void onClickRecentButton() { 1367 if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); 1368 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 1369 int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) 1370 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; 1371 mHandler.removeMessages(msg); 1372 mHandler.sendEmptyMessage(msg); 1373 } 1374 } 1375 1376 public void onClickInputMethodSwitchButton() { 1377 if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled); 1378 int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ? 1379 MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL; 1380 mHandler.removeMessages(msg); 1381 mHandler.sendEmptyMessage(msg); 1382 } 1383 1384 public void onClickCompatModeButton() { 1385 int msg = (mCompatModePanel.getVisibility() == View.GONE) ? 1386 MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL; 1387 mHandler.removeMessages(msg); 1388 mHandler.sendEmptyMessage(msg); 1389 } 1390 1391 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 1392 return new NotificationClicker(intent, pkg, tag, id); 1393 } 1394 1395 private class NotificationClicker implements View.OnClickListener { 1396 private PendingIntent mIntent; 1397 private String mPkg; 1398 private String mTag; 1399 private int mId; 1400 1401 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 1402 mIntent = intent; 1403 mPkg = pkg; 1404 mTag = tag; 1405 mId = id; 1406 } 1407 1408 public void onClick(View v) { 1409 try { 1410 // The intent we are sending is for the application, which 1411 // won't have permission to immediately start an activity after 1412 // the user switches to home. We know it is safe to do at this 1413 // point, so make sure new activity switches are now allowed. 1414 ActivityManagerNative.getDefault().resumeAppSwitches(); 1415 // Also, notifications can be launched from the lock screen, 1416 // so dismiss the lock screen when the activity starts. 1417 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 1418 } catch (RemoteException e) { 1419 } 1420 1421 if (mIntent != null) { 1422 int[] pos = new int[2]; 1423 v.getLocationOnScreen(pos); 1424 Intent overlay = new Intent(); 1425 overlay.setSourceBounds( 1426 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 1427 try { 1428 mIntent.send(mContext, 0, overlay); 1429 1430 } catch (PendingIntent.CanceledException e) { 1431 // the stack trace isn't very helpful here. Just log the exception message. 1432 Slog.w(TAG, "Sending contentIntent failed: " + e); 1433 } 1434 1435 KeyguardManager kgm = 1436 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 1437 if (kgm != null) kgm.exitKeyguardSecurely(null); 1438 } 1439 1440 try { 1441 mBarService.onNotificationClick(mPkg, mTag, mId); 1442 } catch (RemoteException ex) { 1443 // system process is dead if we're here. 1444 } 1445 1446 // close the shade if it was open 1447 animateCollapse(); 1448 visibilityChanged(false); 1449 1450 // If this click was on the intruder alert, hide that instead 1451// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 1452 } 1453 } 1454 1455 StatusBarNotification removeNotificationViews(IBinder key) { 1456 NotificationData.Entry entry = mNotificationData.remove(key); 1457 if (entry == null) { 1458 Slog.w(TAG, "removeNotification for unknown key: " + key); 1459 return null; 1460 } 1461 // Remove the expanded view. 1462 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 1463 if (rowParent != null) rowParent.removeView(entry.row); 1464 1465 if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { 1466 // must close the peek as well, since it's gone 1467 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1468 } 1469 // Remove the icon. 1470// ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); 1471// if (iconParent != null) iconParent.removeView(entry.icon); 1472 updateNotificationIcons(); 1473 1474 return entry.notification; 1475 } 1476 1477 private class NotificationTriggerTouchListener implements View.OnTouchListener { 1478 VelocityTracker mVT; 1479 float mInitialTouchX, mInitialTouchY; 1480 int mTouchSlop; 1481 1482 public NotificationTriggerTouchListener() { 1483 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1484 } 1485 1486 private Runnable mHiliteOnR = new Runnable() { public void run() { 1487 mNotificationArea.setBackgroundResource( 1488 com.android.internal.R.drawable.list_selector_pressed_holo_dark); 1489 }}; 1490 public void hilite(final boolean on) { 1491 if (on) { 1492 mNotificationArea.postDelayed(mHiliteOnR, 100); 1493 } else { 1494 mNotificationArea.removeCallbacks(mHiliteOnR); 1495 mNotificationArea.setBackgroundDrawable(null); 1496 } 1497 } 1498 1499 public boolean onTouch(View v, MotionEvent event) { 1500// Slog.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)", 1501// event.getX(), 1502// event.getY(), 1503// mInitialTouchX, 1504// mInitialTouchY)); 1505 1506 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 1507 return true; 1508 } 1509 1510 final int action = event.getAction(); 1511 switch (action) { 1512 case MotionEvent.ACTION_DOWN: 1513 mVT = VelocityTracker.obtain(); 1514 mInitialTouchX = event.getX(); 1515 mInitialTouchY = event.getY(); 1516 hilite(true); 1517 // fall through 1518 case MotionEvent.ACTION_OUTSIDE: 1519 case MotionEvent.ACTION_MOVE: 1520 // check for fling 1521 if (mVT != null) { 1522 mVT.addMovement(event); 1523 mVT.computeCurrentVelocity(1000); // pixels per second 1524 // require a little more oomph once we're already in peekaboo mode 1525 if (mVT.getYVelocity() < -mNotificationFlingVelocity) { 1526 animateExpand(); 1527 visibilityChanged(true); 1528 hilite(false); 1529 mVT.recycle(); 1530 mVT = null; 1531 } 1532 } 1533 return true; 1534 case MotionEvent.ACTION_UP: 1535 case MotionEvent.ACTION_CANCEL: 1536 hilite(false); 1537 if (mVT != null) { 1538 if (action == MotionEvent.ACTION_UP 1539 // was this a sloppy tap? 1540 && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop 1541 && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) 1542 // dragging off the bottom doesn't count 1543 && (int)event.getY() < v.getBottom()) { 1544 animateExpand(); 1545 visibilityChanged(true); 1546 v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 1547 v.playSoundEffect(SoundEffectConstants.CLICK); 1548 } 1549 1550 mVT.recycle(); 1551 mVT = null; 1552 return true; 1553 } 1554 } 1555 return false; 1556 } 1557 } 1558 1559 public void resetNotificationPeekFadeTimer() { 1560 if (DEBUG) { 1561 Slog.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY 1562 + "ms from now"); 1563 } 1564 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1565 mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 1566 NOTIFICATION_PEEK_FADE_DELAY); 1567 } 1568 1569 private class NotificationIconTouchListener implements View.OnTouchListener { 1570 VelocityTracker mVT; 1571 int mPeekIndex; 1572 float mInitialTouchX, mInitialTouchY; 1573 int mTouchSlop; 1574 1575 public NotificationIconTouchListener() { 1576 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1577 } 1578 1579 public boolean onTouch(View v, MotionEvent event) { 1580 boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; 1581 boolean panelShowing = mNotificationPanel.isShowing(); 1582 if (panelShowing) return false; 1583 1584 int numIcons = mIconLayout.getChildCount(); 1585 int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth()); 1586 if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1; 1587 else if (newPeekIndex < 0) newPeekIndex = 0; 1588 1589 final int action = event.getAction(); 1590 switch (action) { 1591 case MotionEvent.ACTION_DOWN: 1592 mVT = VelocityTracker.obtain(); 1593 mInitialTouchX = event.getX(); 1594 mInitialTouchY = event.getY(); 1595 mPeekIndex = -1; 1596 1597 // fall through 1598 case MotionEvent.ACTION_OUTSIDE: 1599 case MotionEvent.ACTION_MOVE: 1600 // peek and switch icons if necessary 1601 1602 if (newPeekIndex != mPeekIndex) { 1603 mPeekIndex = newPeekIndex; 1604 1605 if (DEBUG) Slog.d(TAG, "will peek at notification #" + mPeekIndex); 1606 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 1607 peekMsg.arg1 = mPeekIndex; 1608 1609 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1610 1611 if (peeking) { 1612 // no delay if we're scrubbing left-right 1613 mHandler.sendMessage(peekMsg); 1614 } else { 1615 // wait for fling 1616 mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH); 1617 } 1618 } 1619 1620 // check for fling 1621 if (mVT != null) { 1622 mVT.addMovement(event); 1623 mVT.computeCurrentVelocity(1000); // pixels per second 1624 // require a little more oomph once we're already in peekaboo mode 1625 if (!panelShowing && ( 1626 (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) 1627 || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { 1628 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1629 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 1630 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1631 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 1632 } 1633 } 1634 return true; 1635 case MotionEvent.ACTION_UP: 1636 case MotionEvent.ACTION_CANCEL: 1637 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1638 if (!peeking) { 1639 if (action == MotionEvent.ACTION_UP 1640 // was this a sloppy tap? 1641 && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop 1642 && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) 1643 // dragging off the bottom doesn't count 1644 && (int)event.getY() < v.getBottom()) { 1645 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 1646 peekMsg.arg1 = mPeekIndex; 1647 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1648 mHandler.sendMessage(peekMsg); 1649 1650 v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 1651 v.playSoundEffect(SoundEffectConstants.CLICK); 1652 1653 peeking = true; // not technically true yet, but the next line will run 1654 } 1655 } 1656 1657 if (peeking) { 1658 resetNotificationPeekFadeTimer(); 1659 } 1660 1661 mVT.recycle(); 1662 mVT = null; 1663 return true; 1664 } 1665 return false; 1666 } 1667 } 1668 1669 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 1670 if (DEBUG) { 1671 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 1672 } 1673 // Construct the icon. 1674 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1675 notification.pkg + "/0x" + Integer.toHexString(notification.id), 1676 notification.notification); 1677 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1678 1679 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 1680 notification.notification.icon, 1681 notification.notification.iconLevel, 1682 notification.notification.number, 1683 notification.notification.tickerText); 1684 if (!iconView.set(ic)) { 1685 handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); 1686 return null; 1687 } 1688 // Construct the expanded view. 1689 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 1690 if (!inflateViews(entry, mPile)) { 1691 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 1692 + notification); 1693 return null; 1694 } 1695 1696 // Add the icon. 1697 int pos = mNotificationData.add(entry); 1698 if (DEBUG) { 1699 Slog.d(TAG, "addNotificationViews: added at " + pos); 1700 } 1701 updateNotificationIcons(); 1702 1703 return iconView; 1704 } 1705 1706 private void reloadAllNotificationIcons() { 1707 if (mIconLayout == null) return; 1708 mIconLayout.removeAllViews(); 1709 updateNotificationIcons(); 1710 } 1711 1712 private void updateNotificationIcons() { 1713 // XXX: need to implement a new limited linear layout class 1714 // to avoid removing & readding everything 1715 1716 if (mIconLayout == null) return; 1717 1718 // first, populate the main notification panel 1719 loadNotificationPanel(); 1720 1721 final LinearLayout.LayoutParams params 1722 = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); 1723 1724 // alternate behavior in DND mode 1725 if (mNotificationDNDMode) { 1726 if (mIconLayout.getChildCount() == 0) { 1727 final Notification dndNotification = new Notification.Builder(mContext) 1728 .setContentTitle(mContext.getText(R.string.notifications_off_title)) 1729 .setContentText(mContext.getText(R.string.notifications_off_text)) 1730 .setSmallIcon(R.drawable.ic_notification_dnd) 1731 .setOngoing(true) 1732 .getNotification(); 1733 1734 final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd", 1735 dndNotification); 1736 iconView.setImageResource(R.drawable.ic_notification_dnd); 1737 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1738 iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0); 1739 1740 mNotificationDNDDummyEntry = new NotificationData.Entry( 1741 null, 1742 new StatusBarNotification("", 0, "", 0, 0, Notification.PRIORITY_MAX, dndNotification), 1743 iconView); 1744 1745 mIconLayout.addView(iconView, params); 1746 } 1747 1748 return; 1749 } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) { 1750 // if icons are disabled but we're not in DND mode, this is probably Setup and we should 1751 // just leave the area totally empty 1752 return; 1753 } 1754 1755 int N = mNotificationData.size(); 1756 1757 if (DEBUG) { 1758 Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); 1759 } 1760 1761 ArrayList<View> toShow = new ArrayList<View>(); 1762 1763 // Extra Special Icons 1764 // The IME switcher and compatibility mode icons take the place of notifications. You didn't 1765 // need to see all those new emails, did you? 1766 int maxNotificationIconsCount = mMaxNotificationIcons; 1767 if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount --; 1768 if (mCompatModeButton.getVisibility() != View.GONE) maxNotificationIconsCount --; 1769 1770 for (int i=0; i< maxNotificationIconsCount; i++) { 1771 if (i>=N) break; 1772 toShow.add(mNotificationData.get(N-i-1).icon); 1773 } 1774 1775 ArrayList<View> toRemove = new ArrayList<View>(); 1776 for (int i=0; i<mIconLayout.getChildCount(); i++) { 1777 View child = mIconLayout.getChildAt(i); 1778 if (!toShow.contains(child)) { 1779 toRemove.add(child); 1780 } 1781 } 1782 1783 for (View remove : toRemove) { 1784 mIconLayout.removeView(remove); 1785 } 1786 1787 for (int i=0; i<toShow.size(); i++) { 1788 View v = toShow.get(i); 1789 v.setPadding(mIconHPadding, 0, mIconHPadding, 0); 1790 if (v.getParent() == null) { 1791 mIconLayout.addView(v, i, params); 1792 } 1793 } 1794 } 1795 1796 private void loadNotificationPanel() { 1797 int N = mNotificationData.size(); 1798 1799 ArrayList<View> toShow = new ArrayList<View>(); 1800 1801 for (int i=0; i<N; i++) { 1802 View row = mNotificationData.get(N-i-1).row; 1803 toShow.add(row); 1804 } 1805 1806 ArrayList<View> toRemove = new ArrayList<View>(); 1807 for (int i=0; i<mPile.getChildCount(); i++) { 1808 View child = mPile.getChildAt(i); 1809 if (!toShow.contains(child)) { 1810 toRemove.add(child); 1811 } 1812 } 1813 1814 for (View remove : toRemove) { 1815 mPile.removeView(remove); 1816 } 1817 1818 for (int i=0; i<toShow.size(); i++) { 1819 View v = toShow.get(i); 1820 if (v.getParent() == null) { 1821 mPile.addView(v, N-1-i); // the notification panel has newest at the bottom 1822 } 1823 } 1824 1825 mNotificationPanel.setNotificationCount(N); 1826 } 1827 1828 void workAroundBadLayerDrawableOpacity(View v) { 1829 Drawable bgd = v.getBackground(); 1830 if (!(bgd instanceof LayerDrawable)) return; 1831 1832 LayerDrawable d = (LayerDrawable) bgd; 1833 v.setBackgroundDrawable(null); 1834 d.setOpacity(PixelFormat.TRANSLUCENT); 1835 v.setBackgroundDrawable(d); 1836 } 1837 1838 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 1839 StatusBarNotification sbn = entry.notification; 1840 RemoteViews remoteViews = sbn.notification.contentView; 1841 if (remoteViews == null) { 1842 return false; 1843 } 1844 1845 // create the row view 1846 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 1847 Context.LAYOUT_INFLATER_SERVICE); 1848 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 1849 workAroundBadLayerDrawableOpacity(row); 1850 View vetoButton = updateNotificationVetoButton(row, entry.notification); 1851 vetoButton.setContentDescription(mContext.getString( 1852 R.string.accessibility_remove_notification)); 1853 1854 // NB: the large icon is now handled entirely by the template 1855 1856 // bind the click event to the content area 1857 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 1858 // XXX: update to allow controls within notification views 1859 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1860// content.setOnFocusChangeListener(mFocusChangeListener); 1861 PendingIntent contentIntent = sbn.notification.contentIntent; 1862 if (contentIntent != null) { 1863 final View.OnClickListener listener = new NotificationClicker( 1864 contentIntent, sbn.pkg, sbn.tag, sbn.id); 1865 content.setOnClickListener(listener); 1866 } else { 1867 content.setOnClickListener(null); 1868 } 1869 1870 View expanded = null; 1871 Exception exception = null; 1872 try { 1873 expanded = remoteViews.apply(mContext, content); 1874 } 1875 catch (RuntimeException e) { 1876 exception = e; 1877 } 1878 if (expanded == null) { 1879 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 1880 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 1881 return false; 1882 } else { 1883 content.addView(expanded); 1884 row.setDrawingCacheEnabled(true); 1885 } 1886 1887 applyLegacyRowBackground(sbn, content); 1888 1889 entry.row = row; 1890 entry.content = content; 1891 entry.expanded = expanded; 1892 1893 return true; 1894 } 1895 1896 void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 1897 if (sbn.notification.contentView.getLayoutId() != 1898 com.android.internal.R.layout.status_bar_latest_event_content) { 1899 int version = 0; 1900 try { 1901 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); 1902 version = info.targetSdkVersion; 1903 } catch (NameNotFoundException ex) { 1904 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); 1905 } 1906 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 1907 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 1908 } else { 1909 content.setBackgroundResource(R.drawable.notification_row_bg); 1910 } 1911 } 1912 } 1913 1914 public void clearAll() { 1915 try { 1916 mBarService.onClearAllNotifications(); 1917 } catch (RemoteException ex) { 1918 // system process is dead if we're here. 1919 } 1920 animateCollapse(); 1921 visibilityChanged(false); 1922 } 1923 1924 public void toggleRecentApps() { 1925 int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) 1926 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; 1927 mHandler.removeMessages(msg); 1928 mHandler.sendEmptyMessage(msg); 1929 } 1930 1931 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1932 public void onReceive(Context context, Intent intent) { 1933 String action = intent.getAction(); 1934 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 1935 || Intent.ACTION_SCREEN_OFF.equals(action)) { 1936 boolean excludeRecents = false; 1937 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { 1938 String reason = intent.getStringExtra("reason"); 1939 if (reason != null) { 1940 excludeRecents = reason.equals("recentapps"); 1941 } 1942 } 1943 if (Intent.ACTION_SCREEN_OFF.equals(action)) { 1944 // If we're turning the screen off, we want to hide the 1945 // recents panel with no animation 1946 // TODO: hide other things, like the notification tray, 1947 // with no animation as well 1948 mRecentsPanel.show(false, false); 1949 excludeRecents = true; 1950 } 1951 animateCollapse(excludeRecents); 1952 } 1953 } 1954 }; 1955 1956 public class TouchOutsideListener implements View.OnTouchListener { 1957 private int mMsg; 1958 private StatusBarPanel mPanel; 1959 1960 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1961 mMsg = msg; 1962 mPanel = panel; 1963 } 1964 1965 public boolean onTouch(View v, MotionEvent ev) { 1966 final int action = ev.getAction(); 1967 if (action == MotionEvent.ACTION_OUTSIDE 1968 || (action == MotionEvent.ACTION_DOWN 1969 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1970 mHandler.removeMessages(mMsg); 1971 mHandler.sendEmptyMessage(mMsg); 1972 return true; 1973 } 1974 return false; 1975 } 1976 } 1977 1978 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1979 pw.print("mDisabled=0x"); 1980 pw.println(Integer.toHexString(mDisabled)); 1981 pw.println("mNetworkController:"); 1982 mNetworkController.dump(fd, pw, args); 1983 } 1984 1985} 1986 1987 1988