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