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