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