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