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