TabletStatusBar.java revision f66664a50e914e1cc5d66046cfc4c5b195a09eab
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 mNavigationArea.setLayoutTransition(lt); 518 // no multi-touch on the nav buttons 519 mNavigationArea.setMotionEventSplittingEnabled(false); 520 521 // The bar contents buttons 522 mFeedbackIconArea = (ViewGroup)sb.findViewById(R.id.feedbackIconArea); 523 mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton); 524 // Overwrite the lister 525 mInputMethodSwitchButton.setOnClickListener(mOnClickListener); 526 527 mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton); 528 mCompatModeButton.setOnClickListener(mOnClickListener); 529 mCompatModeButton.setVisibility(View.GONE); 530 531 // for redirecting errant bar taps to the IME 532 mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar); 533 534 // "shadows" of the status bar features, for lights-out mode 535 mShadow = sb.findViewById(R.id.bar_shadow); 536 mShadow.setOnTouchListener( 537 new View.OnTouchListener() { 538 public boolean onTouch(View v, MotionEvent ev) { 539 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 540 // even though setting the systemUI visibility below will turn these views 541 // on, we need them to come up faster so that they can catch this motion 542 // event 543 mShadow.setVisibility(View.GONE); 544 mBarContents.setVisibility(View.VISIBLE); 545 546 try { 547 mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE); 548 } catch (RemoteException ex) { 549 // system process dead 550 } 551 } 552 return false; 553 } 554 }); 555 556 // tuning parameters 557 final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 600; 558 final int LIGHTS_GOING_OUT_SHADOW_DURATION = 1000; 559 final int LIGHTS_GOING_OUT_SHADOW_DELAY = 500; 560 561 final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200; 562// final int LIGHTS_COMING_UP_SYSBAR_DELAY = 50; 563 final int LIGHTS_COMING_UP_SHADOW_DURATION = 0; 564 565 LayoutTransition xition = new LayoutTransition(); 566 xition.setAnimator(LayoutTransition.APPEARING, 567 ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)); 568 xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION); 569 xition.setStartDelay(LayoutTransition.APPEARING, 0); 570 xition.setAnimator(LayoutTransition.DISAPPEARING, 571 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 572 xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION); 573 xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 574 ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition); 575 576 xition = new LayoutTransition(); 577 xition.setAnimator(LayoutTransition.APPEARING, 578 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); 579 xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION); 580 xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY); 581 xition.setAnimator(LayoutTransition.DISAPPEARING, 582 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 583 xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION); 584 xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 585 ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition); 586 587 // set the initial view visibility 588 setAreThereNotifications(); 589 590 // Add the windows 591 addPanelWindows(); 592 mRecentButton.setOnTouchListener(mRecentsPanel); 593 594 mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content); 595 mPile.removeAllViews(); 596 597 ScrollView scroller = (ScrollView)mPile.getParent(); 598 scroller.setFillViewport(true); 599 600 mHeightReceiver.addOnBarHeightChangedListener(this); 601 602 // receive broadcasts 603 IntentFilter filter = new IntentFilter(); 604 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 605 filter.addAction(Intent.ACTION_SCREEN_OFF); 606 context.registerReceiver(mBroadcastReceiver, filter); 607 608 return sb; 609 } 610 611 public int getStatusBarHeight() { 612 return mHeightReceiver.getHeight(); 613 } 614 615 protected int getStatusBarGravity() { 616 return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL; 617 } 618 619 public void onBarHeightChanged(int height) { 620 final WindowManager.LayoutParams lp 621 = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams(); 622 if (lp == null) { 623 // haven't been added yet 624 return; 625 } 626 if (lp.height != height) { 627 lp.height = height; 628 final WindowManager wm = WindowManagerImpl.getDefault(); 629 wm.updateViewLayout(mStatusBarView, lp); 630 } 631 } 632 633 private class H extends Handler { 634 public void handleMessage(Message m) { 635 switch (m.what) { 636 case MSG_OPEN_NOTIFICATION_PEEK: 637 if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1); 638 639 if (m.arg1 >= 0) { 640 final int N = mNotificationData.size(); 641 642 if (!mNotificationDNDMode) { 643 if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { 644 NotificationData.Entry entry = mNotificationData.get(N-1-mNotificationPeekIndex); 645 entry.icon.setBackgroundColor(0); 646 mNotificationPeekIndex = -1; 647 mNotificationPeekKey = null; 648 } 649 } 650 651 final int peekIndex = m.arg1; 652 if (peekIndex < N) { 653 //Slog.d(TAG, "loading peek: " + peekIndex); 654 NotificationData.Entry entry = 655 mNotificationDNDMode 656 ? mNotificationDNDDummyEntry 657 : mNotificationData.get(N-1-peekIndex); 658 NotificationData.Entry copy = new NotificationData.Entry( 659 entry.key, 660 entry.notification, 661 entry.icon); 662 inflateViews(copy, mNotificationPeekRow); 663 664 if (mNotificationDNDMode) { 665 copy.content.setOnClickListener(new View.OnClickListener() { 666 public void onClick(View v) { 667 SharedPreferences.Editor editor = Prefs.edit(mContext); 668 editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false); 669 editor.apply(); 670 animateCollapse(); 671 visibilityChanged(false); 672 } 673 }); 674 } 675 676 entry.icon.setBackgroundColor(0x20FFFFFF); 677 678// mNotificationPeekRow.setLayoutTransition( 679// peekIndex < mNotificationPeekIndex 680// ? mNotificationPeekScrubLeft 681// : mNotificationPeekScrubRight); 682 683 mNotificationPeekRow.removeAllViews(); 684 mNotificationPeekRow.addView(copy.row); 685 686 mNotificationPeekWindow.setVisibility(View.VISIBLE); 687 mNotificationPanel.show(false, true); 688 689 mNotificationPeekIndex = peekIndex; 690 mNotificationPeekKey = entry.key; 691 } 692 } 693 break; 694 case MSG_CLOSE_NOTIFICATION_PEEK: 695 if (DEBUG) Slog.d(TAG, "closing notification peek window"); 696 mNotificationPeekWindow.setVisibility(View.GONE); 697 mNotificationPeekRow.removeAllViews(); 698 699 final int N = mNotificationData.size(); 700 if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { 701 NotificationData.Entry entry = 702 mNotificationDNDMode 703 ? mNotificationDNDDummyEntry 704 : mNotificationData.get(N-1-mNotificationPeekIndex); 705 entry.icon.setBackgroundColor(0); 706 } 707 708 mNotificationPeekIndex = -1; 709 mNotificationPeekKey = null; 710 break; 711 case MSG_OPEN_NOTIFICATION_PANEL: 712 if (DEBUG) Slog.d(TAG, "opening notifications panel"); 713 if (!mNotificationPanel.isShowing()) { 714 if (NOTIFICATION_PEEK_ENABLED) { 715 mNotificationPeekWindow.setVisibility(View.GONE); 716 } 717 mNotificationPanel.show(true, true); 718 mNotificationArea.setVisibility(View.INVISIBLE); 719 mTicker.halt(); 720 } 721 break; 722 case MSG_CLOSE_NOTIFICATION_PANEL: 723 if (DEBUG) Slog.d(TAG, "closing notifications panel"); 724 if (mNotificationPanel.isShowing()) { 725 mNotificationPanel.show(false, true); 726 mNotificationArea.setVisibility(View.VISIBLE); 727 } 728 break; 729 case MSG_OPEN_RECENTS_PANEL: 730 if (DEBUG) Slog.d(TAG, "opening recents panel"); 731 if (mRecentsPanel != null) { 732 mRecentsPanel.show(true, true); 733 } 734 break; 735 case MSG_CLOSE_RECENTS_PANEL: 736 if (DEBUG) Slog.d(TAG, "closing recents panel"); 737 if (mRecentsPanel != null && mRecentsPanel.isShowing()) { 738 mRecentsPanel.show(false, true); 739 } 740 break; 741 case MSG_OPEN_INPUT_METHODS_PANEL: 742 if (DEBUG) Slog.d(TAG, "opening input methods panel"); 743 if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel(); 744 break; 745 case MSG_CLOSE_INPUT_METHODS_PANEL: 746 if (DEBUG) Slog.d(TAG, "closing input methods panel"); 747 if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false); 748 break; 749 case MSG_OPEN_COMPAT_MODE_PANEL: 750 if (DEBUG) Slog.d(TAG, "opening compat panel"); 751 if (mCompatModePanel != null) mCompatModePanel.openPanel(); 752 break; 753 case MSG_CLOSE_COMPAT_MODE_PANEL: 754 if (DEBUG) Slog.d(TAG, "closing compat panel"); 755 if (mCompatModePanel != null) mCompatModePanel.closePanel(); 756 break; 757 case MSG_SHOW_CHROME: 758 if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)"); 759 mBarContents.setVisibility(View.VISIBLE); 760 mShadow.setVisibility(View.GONE); 761 mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; 762 notifyUiVisibilityChanged(); 763 break; 764 case MSG_HIDE_CHROME: 765 if (DEBUG) Slog.d(TAG, "showing shadows (lights out)"); 766 animateCollapse(); 767 visibilityChanged(false); 768 mBarContents.setVisibility(View.GONE); 769 mShadow.setVisibility(View.VISIBLE); 770 mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE; 771 notifyUiVisibilityChanged(); 772 break; 773 case MSG_STOP_TICKER: 774 mTicker.halt(); 775 break; 776 } 777 } 778 } 779 780 public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 781 if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon); 782 } 783 784 public void updateIcon(String slot, int index, int viewIndex, 785 StatusBarIcon old, StatusBarIcon icon) { 786 if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon); 787 } 788 789 public void removeIcon(String slot, int index, int viewIndex) { 790 if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")"); 791 } 792 793 public void addNotification(IBinder key, StatusBarNotification notification) { 794 if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); 795 addNotificationViews(key, notification); 796 797 final boolean immersive = isImmersive(); 798 if (false && immersive) { 799 // TODO: immersive mode popups for tablet 800 } else if (notification.notification.fullScreenIntent != null) { 801 // not immersive & a full-screen alert should be shown 802 Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;" 803 + " sending fullScreenIntent"); 804 try { 805 notification.notification.fullScreenIntent.send(); 806 } catch (PendingIntent.CanceledException e) { 807 } 808 } else { 809 tick(key, notification, true); 810 } 811 812 setAreThereNotifications(); 813 } 814 815 public void updateNotification(IBinder key, StatusBarNotification notification) { 816 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 817 818 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 819 if (oldEntry == null) { 820 Slog.w(TAG, "updateNotification for unknown key: " + key); 821 return; 822 } 823 824 final StatusBarNotification oldNotification = oldEntry.notification; 825 final RemoteViews oldContentView = oldNotification.notification.contentView; 826 827 final RemoteViews contentView = notification.notification.contentView; 828 829 if (DEBUG) { 830 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 831 + " ongoing=" + oldNotification.isOngoing() 832 + " expanded=" + oldEntry.expanded 833 + " contentView=" + oldContentView 834 + " rowParent=" + oldEntry.row.getParent()); 835 Slog.d(TAG, "new notification: when=" + notification.notification.when 836 + " ongoing=" + oldNotification.isOngoing() 837 + " contentView=" + contentView); 838 } 839 840 // Can we just reapply the RemoteViews in place? If when didn't change, the order 841 // didn't change. 842 boolean contentsUnchanged = oldEntry.expanded != null 843 && contentView != null && oldContentView != null 844 && contentView.getPackage() != null 845 && oldContentView.getPackage() != null 846 && oldContentView.getPackage().equals(contentView.getPackage()) 847 && oldContentView.getLayoutId() == contentView.getLayoutId(); 848 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 849 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 850 && notification.priority == oldNotification.priority; 851 // priority now encompasses isOngoing() 852 boolean updateTicker = notification.notification.tickerText != null 853 && !TextUtils.equals(notification.notification.tickerText, 854 oldEntry.notification.notification.tickerText); 855 boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1; 856 if (contentsUnchanged && (orderUnchanged || isLastAnyway)) { 857 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 858 oldEntry.notification = notification; 859 try { 860 // Reapply the RemoteViews 861 contentView.reapply(mContext, oldEntry.content); 862 // update the contentIntent 863 final PendingIntent contentIntent = notification.notification.contentIntent; 864 if (contentIntent != null) { 865 final View.OnClickListener listener = new NotificationClicker(contentIntent, 866 notification.pkg, notification.tag, notification.id); 867 oldEntry.largeIcon.setOnClickListener(listener); 868 oldEntry.content.setOnClickListener(listener); 869 } else { 870 oldEntry.largeIcon.setOnClickListener(null); 871 oldEntry.content.setOnClickListener(null); 872 } 873 // Update the icon. 874 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 875 notification.notification.icon, notification.notification.iconLevel, 876 notification.notification.number, 877 notification.notification.tickerText); 878 if (!oldEntry.icon.set(ic)) { 879 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 880 return; 881 } 882 // Update the large icon 883 if (notification.notification.largeIcon != null) { 884 oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon); 885 } else { 886 oldEntry.largeIcon.getLayoutParams().width = 0; 887 oldEntry.largeIcon.setVisibility(View.INVISIBLE); 888 } 889 890 if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { 891 // must update the peek window 892 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 893 peekMsg.arg1 = mNotificationPeekIndex; 894 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 895 mHandler.sendMessage(peekMsg); 896 } 897 } 898 catch (RuntimeException e) { 899 // It failed to add cleanly. Log, and remove the view from the panel. 900 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 901 removeNotificationViews(key); 902 addNotificationViews(key, notification); 903 } 904 } else { 905 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 906 removeNotificationViews(key); 907 addNotificationViews(key, notification); 908 } 909 910 // Restart the ticker if it's still running 911 if (updateTicker) { 912 mTicker.halt(); 913 tick(key, notification, false); 914 } 915 916 setAreThereNotifications(); 917 } 918 919 public void removeNotification(IBinder key) { 920 if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ")"); 921 removeNotificationViews(key); 922 mTicker.remove(key); 923 setAreThereNotifications(); 924 } 925 926 public void showClock(boolean show) { 927 View clock = mBarContents.findViewById(R.id.clock); 928 View network_text = mBarContents.findViewById(R.id.network_text); 929 if (clock != null) { 930 clock.setVisibility(show ? View.VISIBLE : View.GONE); 931 } 932 if (network_text != null) { 933 network_text.setVisibility((!show) ? View.VISIBLE : View.GONE); 934 } 935 } 936 937 public void disable(int state) { 938 int old = mDisabled; 939 int diff = state ^ old; 940 mDisabled = state; 941 942 // act accordingly 943 if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { 944 boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; 945 Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes")); 946 showClock(show); 947 } 948 if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { 949 boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0; 950 Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes")); 951 mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE); 952 } 953 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 954 if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { 955 Slog.i(TAG, "DISABLE_EXPAND: yes"); 956 animateCollapse(); 957 visibilityChanged(false); 958 } 959 } 960 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 961 mNotificationDNDMode = Prefs.read(mContext) 962 .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT); 963 964 if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 965 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":"")); 966 mTicker.halt(); 967 } else { 968 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":"")); 969 } 970 971 // refresh icons to show either notifications or the DND message 972 reloadAllNotificationIcons(); 973 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 974 if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 975 mTicker.halt(); 976 } 977 } 978 if ((diff & (StatusBarManager.DISABLE_RECENT 979 | StatusBarManager.DISABLE_BACK 980 | StatusBarManager.DISABLE_HOME)) != 0) { 981 setNavigationVisibility(state); 982 983 if ((state & StatusBarManager.DISABLE_RECENT) != 0) { 984 // close recents if it's visible 985 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 986 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 987 } 988 } 989 } 990 991 private void setNavigationVisibility(int visibility) { 992 boolean disableHome = ((visibility & StatusBarManager.DISABLE_HOME) != 0); 993 boolean disableRecent = ((visibility & StatusBarManager.DISABLE_RECENT) != 0); 994 boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0); 995 996 mBackButton.setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); 997 mHomeButton.setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); 998 mRecentButton.setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); 999 1000 mInputMethodSwitchButton.setScreenLocked( 1001 (visibility & StatusBarManager.DISABLE_SYSTEM_INFO) != 0); 1002 } 1003 1004 private boolean hasTicker(Notification n) { 1005 return n.tickerView != null || !TextUtils.isEmpty(n.tickerText); 1006 } 1007 1008 private void tick(IBinder key, StatusBarNotification n, boolean firstTime) { 1009 // Don't show the ticker when the windowshade is open. 1010 if (mNotificationPanel.isShowing()) { 1011 return; 1012 } 1013 // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification 1014 // if it's a new notification. 1015 if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) { 1016 return; 1017 } 1018 // Show the ticker if one is requested. Also don't do this 1019 // until status bar window is attached to the window manager, 1020 // because... well, what's the point otherwise? And trying to 1021 // run a ticker without being attached will crash! 1022 if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) { 1023 if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS 1024 | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { 1025 mTicker.add(key, n); 1026 mFeedbackIconArea.setVisibility(View.GONE); 1027 } 1028 } 1029 } 1030 1031 // called by TabletTicker when it's done with all queued ticks 1032 public void doneTicking() { 1033 mFeedbackIconArea.setVisibility(View.VISIBLE); 1034 } 1035 1036 public void animateExpand() { 1037 if (NOTIFICATION_PEEK_ENABLED) { 1038 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1039 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1040 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1041 } 1042 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 1043 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 1044 } 1045 1046 public void animateCollapse() { 1047 animateCollapse(false); 1048 } 1049 1050 private void animateCollapse(boolean excludeRecents) { 1051 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); 1052 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); 1053 if (!excludeRecents) { 1054 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 1055 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 1056 } 1057 mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL); 1058 mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL); 1059 mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL); 1060 mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL); 1061 if (NOTIFICATION_PEEK_ENABLED) { 1062 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1063 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1064 } 1065 } 1066 1067 /** 1068 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 1069 * This was added last-minute and is inconsistent with the way the rest of the notifications 1070 * are handled, because the notification isn't really cancelled. The lights are just 1071 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1072 * this is what he wants. (see bug 1131461) 1073 */ 1074 void visibilityChanged(boolean visible) { 1075 if (mPanelSlightlyVisible != visible) { 1076 mPanelSlightlyVisible = visible; 1077 try { 1078 mBarService.onPanelRevealed(); 1079 } catch (RemoteException ex) { 1080 // Won't fail unless the world has ended. 1081 } 1082 } 1083 } 1084 1085 private void notifyUiVisibilityChanged() { 1086 try { 1087 mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility); 1088 } catch (RemoteException ex) { 1089 } 1090 } 1091 1092 @Override // CommandQueue 1093 public void setSystemUiVisibility(int vis) { 1094 if (vis != mSystemUiVisibility) { 1095 mSystemUiVisibility = vis; 1096 1097 mHandler.removeMessages(MSG_HIDE_CHROME); 1098 mHandler.removeMessages(MSG_SHOW_CHROME); 1099 mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) 1100 ? MSG_SHOW_CHROME : MSG_HIDE_CHROME); 1101 1102 notifyUiVisibilityChanged(); 1103 } 1104 } 1105 1106 public void setLightsOn(boolean on) { 1107 // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app 1108 // that can't handle lights-out mode. 1109 if (mMenuButton.getVisibility() == View.VISIBLE) { 1110 on = true; 1111 } 1112 1113 Slog.v(TAG, "setLightsOn(" + on + ")"); 1114 if (on) { 1115 setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE); 1116 } else { 1117 setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE); 1118 } 1119 } 1120 1121 public void topAppWindowChanged(boolean showMenu) { 1122 if (DEBUG) { 1123 Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); 1124 } 1125 mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE); 1126 1127 // See above re: lights-out policy for legacy apps. 1128 if (showMenu) setLightsOn(true); 1129 1130 mCompatModeButton.refresh(); 1131 if (mCompatModeButton.getVisibility() == View.VISIBLE) { 1132 if (DEBUG_COMPAT_HELP 1133 || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) { 1134 showCompatibilityHelp(); 1135 } 1136 } else { 1137 hideCompatibilityHelp(); 1138 mCompatModePanel.closePanel(); 1139 } 1140 } 1141 1142 private void showCompatibilityHelp() { 1143 if (mCompatibilityHelpDialog != null) { 1144 return; 1145 } 1146 1147 mCompatibilityHelpDialog = View.inflate(mContext, R.layout.compat_mode_help, null); 1148 View button = mCompatibilityHelpDialog.findViewById(R.id.button); 1149 1150 button.setOnClickListener(new View.OnClickListener() { 1151 @Override 1152 public void onClick(View v) { 1153 hideCompatibilityHelp(); 1154 SharedPreferences.Editor editor = Prefs.edit(mContext); 1155 editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true); 1156 editor.apply(); 1157 } 1158 }); 1159 1160 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 1161 ViewGroup.LayoutParams.MATCH_PARENT, 1162 ViewGroup.LayoutParams.MATCH_PARENT, 1163 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG, 1164 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1165 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 1166 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 1167 PixelFormat.TRANSLUCENT); 1168 lp.setTitle("CompatibilityModeDialog"); 1169 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 1170 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 1171 lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade 1172 1173 WindowManagerImpl.getDefault().addView(mCompatibilityHelpDialog, lp); 1174 } 1175 1176 private void hideCompatibilityHelp() { 1177 if (mCompatibilityHelpDialog != null) { 1178 WindowManagerImpl.getDefault().removeView(mCompatibilityHelpDialog); 1179 mCompatibilityHelpDialog = null; 1180 } 1181 } 1182 1183 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { 1184 mInputMethodSwitchButton.setImeWindowStatus(token, 1185 (vis & InputMethodService.IME_ACTIVE) != 0); 1186 updateNotificationIcons(); 1187 mInputMethodsPanel.setImeToken(token); 1188 int res; 1189 switch (backDisposition) { 1190 case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: 1191 res = R.drawable.ic_sysbar_back; 1192 break; 1193 case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: 1194 res = R.drawable.ic_sysbar_back_ime; 1195 break; 1196 case InputMethodService.BACK_DISPOSITION_DEFAULT: 1197 default: 1198 if ((vis & InputMethodService.IME_VISIBLE) != 0) { 1199 res = R.drawable.ic_sysbar_back_ime; 1200 } else { 1201 res = R.drawable.ic_sysbar_back; 1202 } 1203 break; 1204 } 1205 mBackButton.setImageResource(res); 1206 if (FAKE_SPACE_BAR) { 1207 mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0) 1208 ? View.VISIBLE : View.GONE); 1209 } 1210 } 1211 1212 @Override 1213 public void setHardKeyboardStatus(boolean available, boolean enabled) { 1214 if (DEBUG) { 1215 Slog.d(TAG, "Set hard keyboard status: available=" + available 1216 + ", enabled=" + enabled); 1217 } 1218 mInputMethodSwitchButton.setHardKeyboardStatus(available); 1219 updateNotificationIcons(); 1220 mInputMethodsPanel.setHardKeyboardStatus(available, enabled); 1221 } 1222 1223 @Override 1224 public void onHardKeyboardEnabledChange(boolean enabled) { 1225 try { 1226 mBarService.setHardKeyboardEnabled(enabled); 1227 } catch (RemoteException ex) { 1228 } 1229 } 1230 1231 private boolean isImmersive() { 1232 try { 1233 return ActivityManagerNative.getDefault().isTopActivityImmersive(); 1234 //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 1235 } catch (RemoteException ex) { 1236 // the end is nigh 1237 return false; 1238 } 1239 } 1240 1241 private void setAreThereNotifications() { 1242 if (mNotificationPanel != null) { 1243 mNotificationPanel.setClearable(mNotificationData.hasClearableItems()); 1244 } 1245 } 1246 1247 /** 1248 * Cancel this notification and tell the status bar service about the failure. Hold no locks. 1249 */ 1250 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 1251 removeNotification(key); 1252 try { 1253 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 1254 } catch (RemoteException ex) { 1255 // The end is nigh. 1256 } 1257 } 1258 1259 private void sendKey(KeyEvent key) { 1260 try { 1261 if (DEBUG) Slog.d(TAG, "injecting key event: " + key); 1262 mWindowManager.injectInputEventNoWait(key); 1263 } catch (RemoteException ex) { 1264 } 1265 } 1266 1267 private View.OnClickListener mOnClickListener = new View.OnClickListener() { 1268 public void onClick(View v) { 1269 if (v == mRecentButton) { 1270 onClickRecentButton(); 1271 } else if (v == mInputMethodSwitchButton) { 1272 onClickInputMethodSwitchButton(); 1273 } else if (v == mCompatModeButton) { 1274 onClickCompatModeButton(); 1275 } 1276 } 1277 }; 1278 1279 public void onClickRecentButton() { 1280 if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); 1281 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 1282 int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) 1283 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; 1284 mHandler.removeMessages(msg); 1285 mHandler.sendEmptyMessage(msg); 1286 } 1287 } 1288 1289 public void onClickInputMethodSwitchButton() { 1290 if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled); 1291 int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ? 1292 MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL; 1293 mHandler.removeMessages(msg); 1294 mHandler.sendEmptyMessage(msg); 1295 } 1296 1297 public void onClickCompatModeButton() { 1298 int msg = (mCompatModePanel.getVisibility() == View.GONE) ? 1299 MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL; 1300 mHandler.removeMessages(msg); 1301 mHandler.sendEmptyMessage(msg); 1302 } 1303 1304 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 1305 return new NotificationClicker(intent, pkg, tag, id); 1306 } 1307 1308 private class NotificationClicker implements View.OnClickListener { 1309 private PendingIntent mIntent; 1310 private String mPkg; 1311 private String mTag; 1312 private int mId; 1313 1314 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 1315 mIntent = intent; 1316 mPkg = pkg; 1317 mTag = tag; 1318 mId = id; 1319 } 1320 1321 public void onClick(View v) { 1322 try { 1323 // The intent we are sending is for the application, which 1324 // won't have permission to immediately start an activity after 1325 // the user switches to home. We know it is safe to do at this 1326 // point, so make sure new activity switches are now allowed. 1327 ActivityManagerNative.getDefault().resumeAppSwitches(); 1328 // Also, notifications can be launched from the lock screen, 1329 // so dismiss the lock screen when the activity starts. 1330 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 1331 } catch (RemoteException e) { 1332 } 1333 1334 if (mIntent != null) { 1335 int[] pos = new int[2]; 1336 v.getLocationOnScreen(pos); 1337 Intent overlay = new Intent(); 1338 overlay.setSourceBounds( 1339 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 1340 try { 1341 mIntent.send(mContext, 0, overlay); 1342 1343 } catch (PendingIntent.CanceledException e) { 1344 // the stack trace isn't very helpful here. Just log the exception message. 1345 Slog.w(TAG, "Sending contentIntent failed: " + e); 1346 } 1347 1348 KeyguardManager kgm = 1349 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 1350 if (kgm != null) kgm.exitKeyguardSecurely(null); 1351 } 1352 1353 try { 1354 mBarService.onNotificationClick(mPkg, mTag, mId); 1355 } catch (RemoteException ex) { 1356 // system process is dead if we're here. 1357 } 1358 1359 // close the shade if it was open 1360 animateCollapse(); 1361 visibilityChanged(false); 1362 1363 // If this click was on the intruder alert, hide that instead 1364// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 1365 } 1366 } 1367 1368 StatusBarNotification removeNotificationViews(IBinder key) { 1369 NotificationData.Entry entry = mNotificationData.remove(key); 1370 if (entry == null) { 1371 Slog.w(TAG, "removeNotification for unknown key: " + key); 1372 return null; 1373 } 1374 // Remove the expanded view. 1375 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 1376 if (rowParent != null) rowParent.removeView(entry.row); 1377 1378 if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { 1379 // must close the peek as well, since it's gone 1380 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1381 } 1382 // Remove the icon. 1383// ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); 1384// if (iconParent != null) iconParent.removeView(entry.icon); 1385 updateNotificationIcons(); 1386 1387 return entry.notification; 1388 } 1389 1390 private class NotificationTriggerTouchListener implements View.OnTouchListener { 1391 VelocityTracker mVT; 1392 float mInitialTouchX, mInitialTouchY; 1393 int mTouchSlop; 1394 1395 public NotificationTriggerTouchListener() { 1396 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1397 } 1398 1399 private Runnable mHiliteOnR = new Runnable() { public void run() { 1400 mNotificationArea.setBackgroundResource( 1401 com.android.internal.R.drawable.list_selector_pressed_holo_dark); 1402 }}; 1403 public void hilite(final boolean on) { 1404 if (on) { 1405 mNotificationArea.postDelayed(mHiliteOnR, 100); 1406 } else { 1407 mNotificationArea.removeCallbacks(mHiliteOnR); 1408 mNotificationArea.setBackgroundDrawable(null); 1409 } 1410 } 1411 1412 public boolean onTouch(View v, MotionEvent event) { 1413// Slog.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)", 1414// event.getX(), 1415// event.getY(), 1416// mInitialTouchX, 1417// mInitialTouchY)); 1418 1419 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 1420 return true; 1421 } 1422 1423 final int action = event.getAction(); 1424 switch (action) { 1425 case MotionEvent.ACTION_DOWN: 1426 mVT = VelocityTracker.obtain(); 1427 mInitialTouchX = event.getX(); 1428 mInitialTouchY = event.getY(); 1429 hilite(true); 1430 // fall through 1431 case MotionEvent.ACTION_OUTSIDE: 1432 case MotionEvent.ACTION_MOVE: 1433 // check for fling 1434 if (mVT != null) { 1435 mVT.addMovement(event); 1436 mVT.computeCurrentVelocity(1000); // pixels per second 1437 // require a little more oomph once we're already in peekaboo mode 1438 if (mVT.getYVelocity() < -mNotificationFlingVelocity) { 1439 animateExpand(); 1440 visibilityChanged(true); 1441 hilite(false); 1442 mVT.recycle(); 1443 mVT = null; 1444 } 1445 } 1446 return true; 1447 case MotionEvent.ACTION_UP: 1448 case MotionEvent.ACTION_CANCEL: 1449 hilite(false); 1450 if (mVT != null) { 1451 if (action == MotionEvent.ACTION_UP 1452 // was this a sloppy tap? 1453 && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop 1454 && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) 1455 // dragging off the bottom doesn't count 1456 && (int)event.getY() < v.getBottom()) { 1457 animateExpand(); 1458 visibilityChanged(true); 1459 v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 1460 v.playSoundEffect(SoundEffectConstants.CLICK); 1461 } 1462 1463 mVT.recycle(); 1464 mVT = null; 1465 return true; 1466 } 1467 } 1468 return false; 1469 } 1470 } 1471 1472 public void resetNotificationPeekFadeTimer() { 1473 if (DEBUG) { 1474 Slog.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY 1475 + "ms from now"); 1476 } 1477 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1478 mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 1479 NOTIFICATION_PEEK_FADE_DELAY); 1480 } 1481 1482 private class NotificationIconTouchListener implements View.OnTouchListener { 1483 VelocityTracker mVT; 1484 int mPeekIndex; 1485 float mInitialTouchX, mInitialTouchY; 1486 int mTouchSlop; 1487 1488 public NotificationIconTouchListener() { 1489 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1490 } 1491 1492 public boolean onTouch(View v, MotionEvent event) { 1493 boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; 1494 boolean panelShowing = mNotificationPanel.isShowing(); 1495 if (panelShowing) return false; 1496 1497 int numIcons = mIconLayout.getChildCount(); 1498 int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth()); 1499 if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1; 1500 else if (newPeekIndex < 0) newPeekIndex = 0; 1501 1502 final int action = event.getAction(); 1503 switch (action) { 1504 case MotionEvent.ACTION_DOWN: 1505 mVT = VelocityTracker.obtain(); 1506 mInitialTouchX = event.getX(); 1507 mInitialTouchY = event.getY(); 1508 mPeekIndex = -1; 1509 1510 // fall through 1511 case MotionEvent.ACTION_OUTSIDE: 1512 case MotionEvent.ACTION_MOVE: 1513 // peek and switch icons if necessary 1514 1515 if (newPeekIndex != mPeekIndex) { 1516 mPeekIndex = newPeekIndex; 1517 1518 if (DEBUG) Slog.d(TAG, "will peek at notification #" + mPeekIndex); 1519 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 1520 peekMsg.arg1 = mPeekIndex; 1521 1522 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1523 1524 if (peeking) { 1525 // no delay if we're scrubbing left-right 1526 mHandler.sendMessage(peekMsg); 1527 } else { 1528 // wait for fling 1529 mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH); 1530 } 1531 } 1532 1533 // check for fling 1534 if (mVT != null) { 1535 mVT.addMovement(event); 1536 mVT.computeCurrentVelocity(1000); // pixels per second 1537 // require a little more oomph once we're already in peekaboo mode 1538 if (!panelShowing && ( 1539 (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) 1540 || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { 1541 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1542 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 1543 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1544 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 1545 } 1546 } 1547 return true; 1548 case MotionEvent.ACTION_UP: 1549 case MotionEvent.ACTION_CANCEL: 1550 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1551 if (!peeking) { 1552 if (action == MotionEvent.ACTION_UP 1553 // was this a sloppy tap? 1554 && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop 1555 && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) 1556 // dragging off the bottom doesn't count 1557 && (int)event.getY() < v.getBottom()) { 1558 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 1559 peekMsg.arg1 = mPeekIndex; 1560 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1561 mHandler.sendMessage(peekMsg); 1562 1563 v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 1564 v.playSoundEffect(SoundEffectConstants.CLICK); 1565 1566 peeking = true; // not technically true yet, but the next line will run 1567 } 1568 } 1569 1570 if (peeking) { 1571 resetNotificationPeekFadeTimer(); 1572 } 1573 1574 mVT.recycle(); 1575 mVT = null; 1576 return true; 1577 } 1578 return false; 1579 } 1580 } 1581 1582 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 1583 if (DEBUG) { 1584 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 1585 } 1586 // Construct the icon. 1587 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1588 notification.pkg + "/0x" + Integer.toHexString(notification.id), 1589 notification.notification); 1590 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1591 1592 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 1593 notification.notification.icon, 1594 notification.notification.iconLevel, 1595 notification.notification.number, 1596 notification.notification.tickerText); 1597 if (!iconView.set(ic)) { 1598 handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); 1599 return null; 1600 } 1601 // Construct the expanded view. 1602 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 1603 if (!inflateViews(entry, mPile)) { 1604 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 1605 + notification); 1606 return null; 1607 } 1608 1609 // Add the icon. 1610 int pos = mNotificationData.add(entry); 1611 if (DEBUG) { 1612 Slog.d(TAG, "addNotificationViews: added at " + pos); 1613 } 1614 updateNotificationIcons(); 1615 1616 return iconView; 1617 } 1618 1619 private void reloadAllNotificationIcons() { 1620 if (mIconLayout == null) return; 1621 mIconLayout.removeAllViews(); 1622 updateNotificationIcons(); 1623 } 1624 1625 private void updateNotificationIcons() { 1626 // XXX: need to implement a new limited linear layout class 1627 // to avoid removing & readding everything 1628 1629 if (mIconLayout == null) return; 1630 1631 // first, populate the main notification panel 1632 loadNotificationPanel(); 1633 1634 final LinearLayout.LayoutParams params 1635 = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); 1636 1637 // alternate behavior in DND mode 1638 if (mNotificationDNDMode) { 1639 if (mIconLayout.getChildCount() == 0) { 1640 final Notification dndNotification = new Notification.Builder(mContext) 1641 .setContentTitle(mContext.getText(R.string.notifications_off_title)) 1642 .setContentText(mContext.getText(R.string.notifications_off_text)) 1643 .setSmallIcon(R.drawable.ic_notification_dnd) 1644 .setOngoing(true) 1645 .getNotification(); 1646 1647 final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd", 1648 dndNotification); 1649 iconView.setImageResource(R.drawable.ic_notification_dnd); 1650 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1651 iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0); 1652 1653 mNotificationDNDDummyEntry = new NotificationData.Entry( 1654 null, 1655 new StatusBarNotification("", 0, "", 0, 0, dndNotification), 1656 iconView); 1657 1658 mIconLayout.addView(iconView, params); 1659 } 1660 1661 return; 1662 } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) { 1663 // if icons are disabled but we're not in DND mode, this is probably Setup and we should 1664 // just leave the area totally empty 1665 return; 1666 } 1667 1668 int N = mNotificationData.size(); 1669 1670 if (DEBUG) { 1671 Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); 1672 } 1673 1674 ArrayList<View> toShow = new ArrayList<View>(); 1675 1676 // Extra Special Icons 1677 // The IME switcher and compatibility mode icons take the place of notifications. You didn't 1678 // need to see all those new emails, did you? 1679 int maxNotificationIconsCount = mMaxNotificationIcons; 1680 if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount --; 1681 if (mCompatModeButton.getVisibility() != View.GONE) maxNotificationIconsCount --; 1682 1683 for (int i=0; i< maxNotificationIconsCount; i++) { 1684 if (i>=N) break; 1685 toShow.add(mNotificationData.get(N-i-1).icon); 1686 } 1687 1688 ArrayList<View> toRemove = new ArrayList<View>(); 1689 for (int i=0; i<mIconLayout.getChildCount(); i++) { 1690 View child = mIconLayout.getChildAt(i); 1691 if (!toShow.contains(child)) { 1692 toRemove.add(child); 1693 } 1694 } 1695 1696 for (View remove : toRemove) { 1697 mIconLayout.removeView(remove); 1698 } 1699 1700 for (int i=0; i<toShow.size(); i++) { 1701 View v = toShow.get(i); 1702 v.setPadding(mIconHPadding, 0, mIconHPadding, 0); 1703 if (v.getParent() == null) { 1704 mIconLayout.addView(v, i, params); 1705 } 1706 } 1707 } 1708 1709 private void loadNotificationPanel() { 1710 int N = mNotificationData.size(); 1711 1712 ArrayList<View> toShow = new ArrayList<View>(); 1713 1714 for (int i=0; i<N; i++) { 1715 View row = mNotificationData.get(N-i-1).row; 1716 toShow.add(row); 1717 } 1718 1719 ArrayList<View> toRemove = new ArrayList<View>(); 1720 for (int i=0; i<mPile.getChildCount(); i++) { 1721 View child = mPile.getChildAt(i); 1722 if (!toShow.contains(child)) { 1723 toRemove.add(child); 1724 } 1725 } 1726 1727 for (View remove : toRemove) { 1728 mPile.removeView(remove); 1729 } 1730 1731 for (int i=0; i<toShow.size(); i++) { 1732 View v = toShow.get(i); 1733 if (v.getParent() == null) { 1734 mPile.addView(v, N-1-i); // the notification panel has newest at the bottom 1735 } 1736 } 1737 1738 mNotificationPanel.setNotificationCount(N); 1739 } 1740 1741 void workAroundBadLayerDrawableOpacity(View v) { 1742 Drawable bgd = v.getBackground(); 1743 if (!(bgd instanceof LayerDrawable)) return; 1744 1745 LayerDrawable d = (LayerDrawable) bgd; 1746 v.setBackgroundDrawable(null); 1747 d.setOpacity(PixelFormat.TRANSLUCENT); 1748 v.setBackgroundDrawable(d); 1749 } 1750 1751 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 1752 StatusBarNotification sbn = entry.notification; 1753 RemoteViews remoteViews = sbn.notification.contentView; 1754 if (remoteViews == null) { 1755 return false; 1756 } 1757 1758 // create the row view 1759 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 1760 Context.LAYOUT_INFLATER_SERVICE); 1761 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 1762 workAroundBadLayerDrawableOpacity(row); 1763 View vetoButton = updateNotificationVetoButton(row, entry.notification); 1764 vetoButton.setContentDescription(mContext.getString( 1765 R.string.accessibility_remove_notification)); 1766 1767 // the large icon 1768 ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); 1769 if (sbn.notification.largeIcon != null) { 1770 largeIcon.setImageBitmap(sbn.notification.largeIcon); 1771 largeIcon.setContentDescription(sbn.notification.tickerText); 1772 } else { 1773 largeIcon.getLayoutParams().width = 0; 1774 largeIcon.setVisibility(View.INVISIBLE); 1775 } 1776 largeIcon.setContentDescription(sbn.notification.tickerText); 1777 1778 // bind the click event to the content area 1779 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 1780 // XXX: update to allow controls within notification views 1781 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1782// content.setOnFocusChangeListener(mFocusChangeListener); 1783 PendingIntent contentIntent = sbn.notification.contentIntent; 1784 if (contentIntent != null) { 1785 final View.OnClickListener listener = new NotificationClicker( 1786 contentIntent, sbn.pkg, sbn.tag, sbn.id); 1787 largeIcon.setOnClickListener(listener); 1788 content.setOnClickListener(listener); 1789 } else { 1790 largeIcon.setOnClickListener(null); 1791 content.setOnClickListener(null); 1792 } 1793 1794 View expanded = null; 1795 Exception exception = null; 1796 try { 1797 expanded = remoteViews.apply(mContext, content); 1798 } 1799 catch (RuntimeException e) { 1800 exception = e; 1801 } 1802 if (expanded == null) { 1803 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 1804 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 1805 return false; 1806 } else { 1807 content.addView(expanded); 1808 row.setDrawingCacheEnabled(true); 1809 } 1810 1811 applyLegacyRowBackground(sbn, content); 1812 1813 entry.row = row; 1814 entry.content = content; 1815 entry.expanded = expanded; 1816 entry.largeIcon = largeIcon; 1817 1818 return true; 1819 } 1820 1821 void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 1822 if (sbn.notification.contentView.getLayoutId() != 1823 com.android.internal.R.layout.status_bar_latest_event_content) { 1824 int version = 0; 1825 try { 1826 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); 1827 version = info.targetSdkVersion; 1828 } catch (NameNotFoundException ex) { 1829 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); 1830 } 1831 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 1832 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 1833 } else { 1834 content.setBackgroundResource(R.drawable.notification_row_bg); 1835 } 1836 } 1837 } 1838 1839 public void clearAll() { 1840 try { 1841 mBarService.onClearAllNotifications(); 1842 } catch (RemoteException ex) { 1843 // system process is dead if we're here. 1844 } 1845 animateCollapse(); 1846 visibilityChanged(false); 1847 } 1848 1849 public void toggleRecentApps() { 1850 int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) 1851 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; 1852 mHandler.removeMessages(msg); 1853 mHandler.sendEmptyMessage(msg); 1854 } 1855 1856 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1857 public void onReceive(Context context, Intent intent) { 1858 String action = intent.getAction(); 1859 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 1860 || Intent.ACTION_SCREEN_OFF.equals(action)) { 1861 boolean excludeRecents = false; 1862 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { 1863 String reason = intent.getStringExtra("reason"); 1864 if (reason != null) { 1865 excludeRecents = reason.equals("recentapps"); 1866 } 1867 } 1868 if (Intent.ACTION_SCREEN_OFF.equals(action)) { 1869 // If we're turning the screen off, we want to hide the 1870 // recents panel with no animation 1871 // TODO: hide other things, like the notification tray, 1872 // with no animation as well 1873 mRecentsPanel.show(false, false); 1874 excludeRecents = true; 1875 } 1876 animateCollapse(excludeRecents); 1877 } 1878 } 1879 }; 1880 1881 public class TouchOutsideListener implements View.OnTouchListener { 1882 private int mMsg; 1883 private StatusBarPanel mPanel; 1884 1885 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1886 mMsg = msg; 1887 mPanel = panel; 1888 } 1889 1890 public boolean onTouch(View v, MotionEvent ev) { 1891 final int action = ev.getAction(); 1892 if (action == MotionEvent.ACTION_OUTSIDE 1893 || (action == MotionEvent.ACTION_DOWN 1894 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1895 mHandler.removeMessages(mMsg); 1896 mHandler.sendEmptyMessage(mMsg); 1897 return true; 1898 } 1899 return false; 1900 } 1901 } 1902 1903 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1904 pw.print("mDisabled=0x"); 1905 pw.println(Integer.toHexString(mDisabled)); 1906 pw.println("mNetworkController:"); 1907 mNetworkController.dump(fd, pw, args); 1908 } 1909} 1910 1911 1912