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