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