/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.windowStateToString; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.Notification; import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.inputmethodservice.InputMethodService; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewPropertyAnimator; import android.view.ViewStub; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.DemoMode; import com.android.systemui.EventLogTags; import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.DateView; import com.android.systemui.statusbar.policy.HeadsUpNotificationView; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.MouseController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NotificationRowLayout; import com.android.systemui.statusbar.policy.OnSizeChangedListener; import com.android.systemui.statusbar.policy.RotationLockController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; public class PhoneStatusBar extends BaseStatusBar implements DemoMode { static final String TAG = "PhoneStatusBar"; public static final boolean DEBUG = BaseStatusBar.DEBUG; public static final boolean SPEW = false; public static final boolean DUMPTRUCK = true; // extra dumpsys info public static final boolean DEBUG_GESTURES = false; public static final boolean DEBUG_WINDOW_STATE = false; public static final boolean SETTINGS_DRAG_SHORTCUT = true; // additional instrumentation for testing purposes; intended to be left on during development public static final boolean CHATTY = DEBUG; public static final String ACTION_STATUSBAR_START = "com.android.internal.policy.statusbar.START"; private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; private static final int MSG_CLOSE_PANELS = 1001; private static final int MSG_OPEN_SETTINGS_PANEL = 1002; // 1020-1030 reserved for BaseStatusBar private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER; private static final int STATUS_OR_NAV_TRANSIENT = View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT; private static final long AUTOHIDE_TIMEOUT_MS = 3000; // fling gesture tuning parameters, scaled to display density private float mSelfExpandVelocityPx; // classic value: 2000px/s private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up") private float mFlingExpandMinVelocityPx; // classic value: 200px/s private float mFlingCollapseMinVelocityPx; // classic value: 200px/s private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1) private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand) private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s private float mExpandAccelPx; // classic value: 2000px/s/s private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up") private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little // faster than mSelfCollapseVelocityPx) PhoneStatusBarPolicy mIconPolicy; // These are no longer handled by the policy, because we need custom strategies for them BluetoothController mBluetoothController; BatteryController mBatteryController; LocationController mLocationController; MouseController mMouseController; NetworkController mNetworkController; RotationLockController mRotationLockController; int mNaturalBarHeight = -1; int mIconSize = -1; int mIconHPadding = -1; Display mDisplay; Point mCurrentDisplaySize = new Point(); private float mHeadsUpVerticalOffset; private int[] mPilePosition = new int[2]; StatusBarWindowView mStatusBarWindow; PhoneStatusBarView mStatusBarView; private int mStatusBarWindowState = WINDOW_STATE_SHOWING; int mPixelFormat; Object mQueueLock = new Object(); // viewgroup containing the normal contents of the statusbar LinearLayout mStatusBarContents; // right-hand icons LinearLayout mSystemIconArea; // left-hand icons LinearLayout mStatusIcons; // the icons themselves IconMerger mNotificationIcons; // [+> View mMoreIcon; // expanded notifications NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window ScrollView mScrollView; View mExpandedContents; int mNotificationPanelGravity; int mNotificationPanelMarginBottomPx, mNotificationPanelMarginPx; float mNotificationPanelMinHeightFrac; boolean mNotificationPanelIsFullScreenWidth; TextView mNotificationPanelDebugText; // settings QuickSettings mQS; boolean mHasSettingsPanel, mHasFlipSettings; SettingsPanelView mSettingsPanel; View mFlipSettingsView; QuickSettingsContainerView mSettingsContainer; int mSettingsPanelGravity; // top bar View mNotificationPanelHeader; View mDateTimeView; View mClearButton; ImageView mSettingsButton, mNotificationButton; // carrier/wifi label private TextView mCarrierLabel; private boolean mCarrierLabelVisible = false; private int mCarrierLabelHeight; private TextView mEmergencyCallLabel; private int mNotificationHeaderHeight; private boolean mShowCarrierInPanel = false; // position int[] mPositionTmp = new int[2]; boolean mExpandedVisible; // the date view DateView mDateView; // for heads up notifications private HeadsUpNotificationView mHeadsUpNotificationView; private int mHeadsUpNotificationDecay; // on-screen navigation buttons private NavigationBarView mNavigationBarView = null; private int mNavigationBarWindowState = WINDOW_STATE_SHOWING; // the tracker view int mTrackingPosition; // the position of the top of the tracking view. // ticker private Ticker mTicker; private View mTickerView; private boolean mTicking; // Tracking finger for opening/closing. int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore boolean mTracking; VelocityTracker mVelocityTracker; int[] mAbsPos = new int[2]; Runnable mPostCollapseCleanup = null; // for disabling the status bar int mDisabled = 0; // tracking calls to View.setSystemUiVisibility() int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; DisplayMetrics mDisplayMetrics = new DisplayMetrics(); // XXX: gesture research private final GestureRecorder mGestureRec = DEBUG_GESTURES ? new GestureRecorder("/sdcard/statusbar_gestures.dat") : null; private int mNavigationIconHints = 0; private final Animator.AnimatorListener mMakeIconsInvisible = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // double-check to avoid races if (mStatusBarContents.getAlpha() == 0) { if (DEBUG) Log.d(TAG, "makeIconsInvisible"); mStatusBarContents.setVisibility(View.INVISIBLE); } } }; // ensure quick settings is disabled until the current user makes it through the setup wizard private boolean mUserSetup = false; private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { final boolean userSetup = 0 != Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0 /*default */, mCurrentUserId); if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " + "selfChange=%s userSetup=%s mUserSetup=%s", selfChange, userSetup, mUserSetup)); if (mSettingsButton != null && mHasFlipSettings) { mSettingsButton.setVisibility(userSetup ? View.VISIBLE : View.INVISIBLE); } if (mSettingsPanel != null) { mSettingsPanel.setEnabled(userSetup); } if (userSetup != mUserSetup) { mUserSetup = userSetup; if (!mUserSetup && mStatusBarView != null) animateCollapseQuickSettings(); } } }; final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { boolean wasUsing = mUseHeadsUp; mUseHeadsUp = ENABLE_HEADS_UP && 0 != Settings.Global.getInt( mContext.getContentResolver(), SETTING_HEADS_UP, 0); Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); if (wasUsing != mUseHeadsUp) { if (!mUseHeadsUp) { Log.d(TAG, "dismissing any existing heads up notification on disable event"); mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); removeHeadsUpView(); } else { addHeadsUpView(); } } } }; private int mInteractingWindows; private boolean mAutohideSuspended; private int mStatusBarMode; private int mNavigationBarMode; private Boolean mScreenOn; private final Runnable mAutohide = new Runnable() { @Override public void run() { int requested = mSystemUiVisibility & ~STATUS_OR_NAV_TRANSIENT; if (mSystemUiVisibility != requested) { notifyUiVisibilityChanged(requested); } }}; @Override public void start() { mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); updateDisplaySize(); super.start(); // calls createAndAddWindows() addNavigationBar(); // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext); mHeadsUpObserver.onChange(true); // set up if (ENABLE_HEADS_UP) { mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP), true, mHeadsUpObserver); } } // ================================================================================ // Constructing the view // ================================================================================ protected PhoneStatusBarView makeStatusBarView() { final Context context = mContext; Resources res = context.getResources(); updateDisplaySize(); // populates mDisplayMetrics loadDimens(); mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null); mStatusBarWindow.mService = this; mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { checkUserAutohide(v, event); if (event.getAction() == MotionEvent.ACTION_DOWN) { if (mExpandedVisible) { animateCollapsePanels(); } } return mStatusBarWindow.onTouchEvent(event); }}); mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar); mStatusBarView.setBar(this); PanelHolder holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder); mStatusBarView.setPanelHolder(holder); mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(R.id.notification_panel); mNotificationPanel.setStatusBar(this); mNotificationPanelIsFullScreenWidth = (mNotificationPanel.getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT); // make the header non-responsive to clicks mNotificationPanel.findViewById(R.id.header).setOnTouchListener( new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; // e eats everything } }); if (!ActivityManager.isHighEndGfx()) { mStatusBarWindow.setBackground(null); mNotificationPanel.setBackground(new FastColorDrawable(context.getResources().getColor( R.color.notification_panel_solid_background))); } if (ENABLE_HEADS_UP) { mHeadsUpNotificationView = (HeadsUpNotificationView) View.inflate(context, R.layout.heads_up, null); mHeadsUpNotificationView.setVisibility(View.GONE); mHeadsUpNotificationView.setBar(this); } if (MULTIUSER_DEBUG) { mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(R.id.header_debug_info); mNotificationPanelDebugText.setVisibility(View.VISIBLE); } updateShowSearchHoldoff(); try { boolean showNav = mWindowManagerService.hasNavigationBar(); if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav); if (showNav) { mNavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); mNavigationBarView.setDisabledFlags(mDisabled); mNavigationBarView.setBar(this); mNavigationBarView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { checkUserAutohide(v, event); return false; }}); } } catch (RemoteException ex) { // no window manager? good luck with that } // figure out which pixel-format to use for the status bar. mPixelFormat = PixelFormat.OPAQUE; mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area); mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons); mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons); mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon); mNotificationIcons.setOverflowIndicator(mMoreIcon); mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents); mTickerView = mStatusBarView.findViewById(R.id.ticker); mPile = (NotificationRowLayout)mStatusBarWindow.findViewById(R.id.latestItems); mPile.setLayoutTransitionsEnabled(false); mPile.setLongPressListener(getNotificationLongClicker()); mExpandedContents = mPile; // was: expanded.findViewById(R.id.notificationLinearLayout); mNotificationPanelHeader = mStatusBarWindow.findViewById(R.id.header); mClearButton = mStatusBarWindow.findViewById(R.id.clear_all_button); mClearButton.setOnClickListener(mClearButtonListener); mClearButton.setAlpha(0f); mClearButton.setVisibility(View.INVISIBLE); mClearButton.setEnabled(false); mDateView = (DateView)mStatusBarWindow.findViewById(R.id.date); mHasSettingsPanel = res.getBoolean(R.bool.config_hasSettingsPanel); mHasFlipSettings = res.getBoolean(R.bool.config_hasFlipSettingsPanel); mDateTimeView = mNotificationPanelHeader.findViewById(R.id.datetime); if (mDateTimeView != null) { mDateTimeView.setOnClickListener(mClockClickListener); mDateTimeView.setEnabled(true); } mSettingsButton = (ImageView) mStatusBarWindow.findViewById(R.id.settings_button); if (mSettingsButton != null) { mSettingsButton.setOnClickListener(mSettingsButtonListener); if (mHasSettingsPanel) { if (mStatusBarView.hasFullWidthNotifications()) { // the settings panel is hiding behind this button mSettingsButton.setImageResource(R.drawable.ic_notify_quicksettings); mSettingsButton.setVisibility(View.VISIBLE); } else { // there is a settings panel, but it's on the other side of the (large) screen final View buttonHolder = mStatusBarWindow.findViewById( R.id.settings_button_holder); if (buttonHolder != null) { buttonHolder.setVisibility(View.GONE); } } } else { // no settings panel, go straight to settings mSettingsButton.setVisibility(View.VISIBLE); mSettingsButton.setImageResource(R.drawable.ic_notify_settings); } } if (mHasFlipSettings) { mNotificationButton = (ImageView) mStatusBarWindow.findViewById(R.id.notification_button); if (mNotificationButton != null) { mNotificationButton.setOnClickListener(mNotificationButtonListener); } } mScrollView = (ScrollView)mStatusBarWindow.findViewById(R.id.scroll); mScrollView.setVerticalScrollBarEnabled(false); // less drawing during pulldowns if (!mNotificationPanelIsFullScreenWidth) { mScrollView.setSystemUiVisibility( View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER | View.STATUS_BAR_DISABLE_NOTIFICATION_ICONS | View.STATUS_BAR_DISABLE_CLOCK); } mTicker = new MyTicker(context, mStatusBarView); TickerView tickerView = (TickerView)mStatusBarView.findViewById(R.id.tickerText); tickerView.mTicker = mTicker; mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); // set the inital view visibility setAreThereNotifications(); // Other icons mLocationController = new LocationController(mContext); // will post a notification mBatteryController = new BatteryController(mContext); mMouseController = new MouseController(mContext); mNetworkController = new NetworkController(mContext); mBluetoothController = new BluetoothController(mContext); mRotationLockController = new RotationLockController(mContext); final SignalClusterView signalCluster = (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster); mNetworkController.addSignalCluster(signalCluster); signalCluster.setNetworkController(mNetworkController); final boolean isAPhone = mNetworkController.hasVoiceCallingFeature(); if (isAPhone) { mEmergencyCallLabel = (TextView) mStatusBarWindow.findViewById(R.id.emergency_calls_only); if (mEmergencyCallLabel != null) { mNetworkController.addEmergencyLabelView(mEmergencyCallLabel); mEmergencyCallLabel.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { }}); mEmergencyCallLabel.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { updateCarrierLabelVisibility(false); }}); } } mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label); mShowCarrierInPanel = (mCarrierLabel != null); if (DEBUG) Log.v(TAG, "carrierlabel=" + mCarrierLabel + " show=" + mShowCarrierInPanel); if (mShowCarrierInPanel) { mCarrierLabel.setVisibility(mCarrierLabelVisible ? View.VISIBLE : View.INVISIBLE); // for mobile devices, we always show mobile connection info here (SPN/PLMN) // for other devices, we show whatever network is connected if (mNetworkController.hasMobileDataFeature()) { mNetworkController.addMobileLabelView(mCarrierLabel); } else { mNetworkController.addCombinedLabelView(mCarrierLabel); } // set up the dynamic hide/show of the label mPile.setOnSizeChangedListener(new OnSizeChangedListener() { @Override public void onSizeChanged(View view, int w, int h, int oldw, int oldh) { updateCarrierLabelVisibility(false); } }); } // Quick Settings (where available, some restrictions apply) if (mHasSettingsPanel) { // first, figure out where quick settings should be inflated final View settings_stub; if (mHasFlipSettings) { // a version of quick settings that flips around behind the notifications settings_stub = mStatusBarWindow.findViewById(R.id.flip_settings_stub); if (settings_stub != null) { mFlipSettingsView = ((ViewStub)settings_stub).inflate(); mFlipSettingsView.setVisibility(View.GONE); mFlipSettingsView.setVerticalScrollBarEnabled(false); } } else { // full quick settings panel settings_stub = mStatusBarWindow.findViewById(R.id.quick_settings_stub); if (settings_stub != null) { mSettingsPanel = (SettingsPanelView) ((ViewStub)settings_stub).inflate(); } else { mSettingsPanel = (SettingsPanelView) mStatusBarWindow.findViewById(R.id.settings_panel); } if (mSettingsPanel != null) { if (!ActivityManager.isHighEndGfx()) { mSettingsPanel.setBackground(new FastColorDrawable(context.getResources().getColor( R.color.notification_panel_solid_background))); } } } // wherever you find it, Quick Settings needs a container to survive mSettingsContainer = (QuickSettingsContainerView) mStatusBarWindow.findViewById(R.id.quick_settings_container); if (mSettingsContainer != null) { mQS = new QuickSettings(mContext, mSettingsContainer); if (!mNotificationPanelIsFullScreenWidth) { mSettingsContainer.setSystemUiVisibility( View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER | View.STATUS_BAR_DISABLE_SYSTEM_INFO); } if (mSettingsPanel != null) { mSettingsPanel.setQuickSettings(mQS); } mQS.setService(this); mQS.setBar(mStatusBarView); mQS.setup(mNetworkController, mBluetoothController, mBatteryController, mLocationController, mRotationLockController, mMouseController); } else { mQS = null; // fly away, be free } } PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mBroadcastReceiver.onReceive(mContext, new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF)); // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(ACTION_DEMO); context.registerReceiver(mBroadcastReceiver, filter); // listen for USER_SETUP_COMPLETE setting (per-user) resetUserSetupObserver(); return mStatusBarView; } @Override protected void onShowSearchPanel() { if (mNavigationBarView != null) { mNavigationBarView.getBarTransitions().setContentVisible(false); } } @Override protected void onHideSearchPanel() { if (mNavigationBarView != null) { mNavigationBarView.getBarTransitions().setContentVisible(true); } } @Override protected View getStatusBarView() { return mStatusBarView; } @Override protected WindowManager.LayoutParams getSearchLayoutParams(LayoutParams layoutParams) { boolean opaque = false; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT)); if (ActivityManager.isHighEndGfx()) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } lp.gravity = Gravity.BOTTOM | Gravity.START; lp.setTitle("SearchPanel"); // TODO: Define custom animation for Search panel lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications; lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; return lp; } @Override protected void updateSearchPanel() { super.updateSearchPanel(); if (mNavigationBarView != null) { mNavigationBarView.setDelegateView(mSearchPanelView); } } @Override public void showSearchPanel() { super.showSearchPanel(); mHandler.removeCallbacks(mShowSearchPanel); // we want to freeze the sysui state wherever it is mSearchPanelView.setSystemUiVisibility(mSystemUiVisibility); if (mNavigationBarView != null) { WindowManager.LayoutParams lp = (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams(); lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWindowManager.updateViewLayout(mNavigationBarView, lp); } } @Override public void hideSearchPanel() { super.hideSearchPanel(); if (mNavigationBarView != null) { WindowManager.LayoutParams lp = (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams(); lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWindowManager.updateViewLayout(mNavigationBarView, lp); } } protected int getStatusBarGravity() { return Gravity.TOP | Gravity.FILL_HORIZONTAL; } public int getStatusBarHeight() { if (mNaturalBarHeight < 0) { final Resources res = mContext.getResources(); mNaturalBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); } return mNaturalBarHeight; } private View.OnClickListener mRecentsClickListener = new View.OnClickListener() { public void onClick(View v) { awakenDreams(); toggleRecentApps(); } }; private int mShowSearchHoldoff = 0; private Runnable mShowSearchPanel = new Runnable() { public void run() { showSearchPanel(); awakenDreams(); } }; View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { switch(event.getAction()) { case MotionEvent.ACTION_DOWN: if (!shouldDisableNavbarGestures()) { mHandler.removeCallbacks(mShowSearchPanel); mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mHandler.removeCallbacks(mShowSearchPanel); awakenDreams(); break; } return false; } }; private void awakenDreams() { if (mDreamManager != null) { try { mDreamManager.awaken(); } catch (RemoteException e) { // fine, stay asleep then } } } private void prepareNavigationBarView() { mNavigationBarView.reorient(); mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener); mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener); mNavigationBarView.getSearchLight().setOnTouchListener(mHomeSearchActionListener); updateSearchPanel(); } // For small-screen devices (read: phones) that lack hardware navigation buttons private void addNavigationBar() { if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mNavigationBarView); if (mNavigationBarView == null) return; prepareNavigationBarView(); mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams()); } private void repositionNavigationBar() { if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return; prepareNavigationBarView(); mWindowManager.updateViewLayout(mNavigationBarView, getNavigationBarLayoutParams()); } private void notifyNavigationBarScreenOn(boolean screenOn) { if (mNavigationBarView == null) return; mNavigationBarView.notifyScreenOn(screenOn); } private WindowManager.LayoutParams getNavigationBarLayoutParams() { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 0 | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); // this will allow the navbar to run in an overlay on devices that support this if (ActivityManager.isHighEndGfx()) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } lp.setTitle("NavigationBar"); lp.windowAnimations = 0; return lp; } private void addHeadsUpView() { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar! WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; lp.gravity = Gravity.TOP; lp.y = getStatusBarHeight(); lp.setTitle("Heads Up"); lp.packageName = mContext.getPackageName(); lp.windowAnimations = R.style.Animation_StatusBar_HeadsUp; mWindowManager.addView(mHeadsUpNotificationView, lp); } private void removeHeadsUpView() { mWindowManager.removeView(mHeadsUpNotificationView); } public void refreshAllStatusBarIcons() { refreshAllIconsForLayout(mStatusIcons); refreshAllIconsForLayout(mNotificationIcons); } private void refreshAllIconsForLayout(LinearLayout ll) { final int count = ll.getChildCount(); for (int n = 0; n < count; n++) { View child = ll.getChildAt(n); if (child instanceof StatusBarIconView) { ((StatusBarIconView) child).updateDrawable(); } } } public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { if (SPEW) Log.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex + " icon=" + icon); StatusBarIconView view = new StatusBarIconView(mContext, slot, null); view.set(icon); mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize)); } public void updateIcon(String slot, int index, int viewIndex, StatusBarIcon old, StatusBarIcon icon) { if (SPEW) Log.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex + " old=" + old + " icon=" + icon); StatusBarIconView view = (StatusBarIconView)mStatusIcons.getChildAt(viewIndex); view.set(icon); } public void removeIcon(String slot, int index, int viewIndex) { if (SPEW) Log.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex); mStatusIcons.removeViewAt(viewIndex); } public void addNotification(IBinder key, StatusBarNotification notification) { if (DEBUG) Log.d(TAG, "addNotification score=" + notification.getScore()); Entry shadeEntry = createNotificationViews(key, notification); if (shadeEntry == null) { return; } if (mUseHeadsUp && shouldInterrupt(notification)) { if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); Entry interruptionCandidate = new Entry(key, notification, null); if (inflateViews(interruptionCandidate, mHeadsUpNotificationView.getHolder())) { mInterruptingNotificationTime = System.currentTimeMillis(); mInterruptingNotificationEntry = interruptionCandidate; shadeEntry.setInterruption(); // 1. Populate mHeadsUpNotificationView mHeadsUpNotificationView.setNotification(mInterruptingNotificationEntry); // 2. Animate mHeadsUpNotificationView in mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); // 3. Set alarm to age the notification off resetHeadsUpDecayTimer(); } } else if (notification.getNotification().fullScreenIntent != null) { // Stop screensaver if the notification has a full-screen intent. // (like an incoming phone call) awakenDreams(); // not immersive & a full-screen alert should be shown if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); try { notification.getNotification().fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } } else { // usual case: status bar visible & not immersive // show the ticker if there isn't already a heads up if (mInterruptingNotificationEntry == null) { tick(null, notification, true); } } addNotificationViews(shadeEntry); // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); } @Override public void resetHeadsUpDecayTimer() { if (mUseHeadsUp && mHeadsUpNotificationDecay > 0 && mHeadsUpNotificationView.isClearable()) { mHandler.removeMessages(MSG_HIDE_HEADS_UP); mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, mHeadsUpNotificationDecay); } } public void removeNotification(IBinder key) { StatusBarNotification old = removeNotificationViews(key); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { // Cancel the ticker if it's still running mTicker.removeEntry(old); // Recalculate the position of the sliding windows and the titles. updateExpandedViewPos(EXPANDED_LEAVE_ALONE); if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null && old == mInterruptingNotificationEntry.notification) { mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); } if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0 && !mNotificationPanel.isTracking()) { animateCollapsePanels(); } } setAreThereNotifications(); } @Override protected void refreshLayout(int layoutDirection) { if (mNavigationBarView != null) { mNavigationBarView.setLayoutDirection(layoutDirection); } if (mClearButton != null && mClearButton instanceof ImageView) { // Force asset reloading ((ImageView)mClearButton).setImageDrawable(null); ((ImageView)mClearButton).setImageResource(R.drawable.ic_notify_clear); } if (mSettingsButton != null) { // Force asset reloading mSettingsButton.setImageDrawable(null); mSettingsButton.setImageResource(R.drawable.ic_notify_quicksettings); } if (mNotificationButton != null) { // Force asset reloading mNotificationButton.setImageDrawable(null); mNotificationButton.setImageResource(R.drawable.ic_notifications); } refreshAllStatusBarIcons(); } private void updateShowSearchHoldoff() { mShowSearchHoldoff = mContext.getResources().getInteger( R.integer.config_show_search_delay); } private void loadNotificationShade() { if (mPile == null) return; int N = mNotificationData.size(); ArrayList<View> toShow = new ArrayList<View>(); final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { Entry ent = mNotificationData.get(N-i-1); if (!(provisioned || showNotificationEvenIfUnprovisioned(ent.notification))) continue; if (!notificationIsForCurrentUser(ent.notification)) continue; toShow.add(ent.row); } ArrayList<View> toRemove = new ArrayList<View>(); for (int i=0; i<mPile.getChildCount(); i++) { View child = mPile.getChildAt(i); if (!toShow.contains(child)) { toRemove.add(child); } } for (View remove : toRemove) { mPile.removeView(remove); } for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { mPile.addView(v, i); } } if (mSettingsButton != null) { mSettingsButton.setEnabled(isDeviceProvisioned()); } } @Override protected void updateNotificationIcons() { if (mNotificationIcons == null) return; loadNotificationShade(); final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); int N = mNotificationData.size(); if (DEBUG) { Log.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + mNotificationIcons); } ArrayList<View> toShow = new ArrayList<View>(); final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { Entry ent = mNotificationData.get(N-i-1); if (!((provisioned && ent.notification.getScore() >= HIDE_ICONS_BELOW_SCORE) || showNotificationEvenIfUnprovisioned(ent.notification))) continue; if (!notificationIsForCurrentUser(ent.notification)) continue; toShow.add(ent.icon); } ArrayList<View> toRemove = new ArrayList<View>(); for (int i=0; i<mNotificationIcons.getChildCount(); i++) { View child = mNotificationIcons.getChildAt(i); if (!toShow.contains(child)) { toRemove.add(child); } } for (View remove : toRemove) { mNotificationIcons.removeView(remove); } for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { mNotificationIcons.addView(v, i, params); } } } protected void updateCarrierLabelVisibility(boolean force) { if (!mShowCarrierInPanel) return; // The idea here is to only show the carrier label when there is enough room to see it, // i.e. when there aren't enough notifications to fill the panel. if (SPEW) { Log.d(TAG, String.format("pileh=%d scrollh=%d carrierh=%d", mPile.getHeight(), mScrollView.getHeight(), mCarrierLabelHeight)); } final boolean emergencyCallsShownElsewhere = mEmergencyCallLabel != null; final boolean makeVisible = !(emergencyCallsShownElsewhere && mNetworkController.isEmergencyOnly()) && mPile.getHeight() < (mNotificationPanel.getHeight() - mCarrierLabelHeight - mNotificationHeaderHeight) && mScrollView.getVisibility() == View.VISIBLE; if (force || mCarrierLabelVisible != makeVisible) { mCarrierLabelVisible = makeVisible; if (DEBUG) { Log.d(TAG, "making carrier label " + (makeVisible?"visible":"invisible")); } mCarrierLabel.animate().cancel(); if (makeVisible) { mCarrierLabel.setVisibility(View.VISIBLE); } mCarrierLabel.animate() .alpha(makeVisible ? 1f : 0f) //.setStartDelay(makeVisible ? 500 : 0) //.setDuration(makeVisible ? 750 : 100) .setDuration(150) .setListener(makeVisible ? null : new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (!mCarrierLabelVisible) { // race mCarrierLabel.setVisibility(View.INVISIBLE); mCarrierLabel.setAlpha(0f); } } }) .start(); } } @Override protected void setAreThereNotifications() { final boolean any = mNotificationData.size() > 0; final boolean clearable = any && mNotificationData.hasClearableItems(); if (SPEW) { Log.d(TAG, "setAreThereNotifications: N=" + mNotificationData.size() + " any=" + any + " clearable=" + clearable); } if (mHasFlipSettings && mFlipSettingsView != null && mFlipSettingsView.getVisibility() == View.VISIBLE && mScrollView.getVisibility() != View.VISIBLE) { // the flip settings panel is unequivocally showing; we should not be shown mClearButton.setVisibility(View.INVISIBLE); } else if (mClearButton.isShown()) { if (clearable != (mClearButton.getAlpha() == 1.0f)) { ObjectAnimator clearAnimation = ObjectAnimator.ofFloat( mClearButton, "alpha", clearable ? 1.0f : 0.0f).setDuration(250); clearAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mClearButton.getAlpha() <= 0.0f) { mClearButton.setVisibility(View.INVISIBLE); } } @Override public void onAnimationStart(Animator animation) { if (mClearButton.getAlpha() <= 0.0f) { mClearButton.setVisibility(View.VISIBLE); } } }); clearAnimation.start(); } } else { mClearButton.setAlpha(clearable ? 1.0f : 0.0f); mClearButton.setVisibility(clearable ? View.VISIBLE : View.INVISIBLE); } mClearButton.setEnabled(clearable); final View nlo = mStatusBarView.findViewById(R.id.notification_lights_out); final boolean showDot = (any&&!areLightsOn()); if (showDot != (nlo.getAlpha() == 1.0f)) { if (showDot) { nlo.setAlpha(0f); nlo.setVisibility(View.VISIBLE); } nlo.animate() .alpha(showDot?1:0) .setDuration(showDot?750:250) .setInterpolator(new AccelerateInterpolator(2.0f)) .setListener(showDot ? null : new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator _a) { nlo.setVisibility(View.GONE); } }) .start(); } updateCarrierLabelVisibility(false); } public void showClock(boolean show) { if (mStatusBarView == null) return; View clock = mStatusBarView.findViewById(R.id.clock); if (clock != null) { clock.setVisibility(show ? View.VISIBLE : View.GONE); } } /** * State is one or more of the DISABLE constants from StatusBarManager. */ public void disable(int state) { final int old = mDisabled; final int diff = state ^ old; mDisabled = state; if (DEBUG) { Log.d(TAG, String.format("disable: 0x%08x -> 0x%08x (diff: 0x%08x)", old, state, diff)); } StringBuilder flagdbg = new StringBuilder(); flagdbg.append("disable: < "); flagdbg.append(((state & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand"); flagdbg.append(((diff & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons"); flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts"); flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "TICKER" : "ticker"); flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info"); flagdbg.append(((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back"); flagdbg.append(((diff & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home"); flagdbg.append(((diff & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent"); flagdbg.append(((diff & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock"); flagdbg.append(((diff & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_SEARCH) != 0) ? "SEARCH" : "search"); flagdbg.append(((diff & StatusBarManager.DISABLE_SEARCH) != 0) ? "* " : " "); flagdbg.append(">"); Log.d(TAG, flagdbg.toString()); if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { mSystemIconArea.animate().cancel(); if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { mSystemIconArea.animate() .alpha(0f) .translationY(mNaturalBarHeight*0.5f) .setDuration(175) .setInterpolator(new DecelerateInterpolator(1.5f)) .setListener(mMakeIconsInvisible) .start(); } else { mSystemIconArea.setVisibility(View.VISIBLE); mSystemIconArea.animate() .alpha(1f) .translationY(0) .setStartDelay(0) .setInterpolator(new DecelerateInterpolator(1.5f)) .setDuration(175) .start(); } } if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; showClock(show); } if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { animateCollapsePanels(); } } if ((diff & (StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_RECENT | StatusBarManager.DISABLE_BACK | StatusBarManager.DISABLE_SEARCH)) != 0) { // the nav bar will take care of these if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state); if ((state & StatusBarManager.DISABLE_RECENT) != 0) { // close recents if it's visible mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); } } if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if (mTicking) { haltTicker(); } mNotificationIcons.animate() .alpha(0f) .translationY(mNaturalBarHeight*0.5f) .setDuration(175) .setInterpolator(new DecelerateInterpolator(1.5f)) .setListener(mMakeIconsInvisible) .start(); } else { mNotificationIcons.setVisibility(View.VISIBLE); mNotificationIcons.animate() .alpha(1f) .translationY(0) .setStartDelay(0) .setInterpolator(new DecelerateInterpolator(1.5f)) .setDuration(175) .start(); } } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { haltTicker(); } } } @Override protected BaseStatusBar.H createHandler() { return new PhoneStatusBar.H(); } /** * All changes to the status bar and notifications funnel through here and are batched. */ private class H extends BaseStatusBar.H { public void handleMessage(Message m) { super.handleMessage(m); switch (m.what) { case MSG_OPEN_NOTIFICATION_PANEL: animateExpandNotificationsPanel(); break; case MSG_OPEN_SETTINGS_PANEL: animateExpandSettingsPanel(); break; case MSG_CLOSE_PANELS: animateCollapsePanels(); break; case MSG_SHOW_HEADS_UP: setHeadsUpVisibility(true); break; case MSG_HIDE_HEADS_UP: setHeadsUpVisibility(false); break; case MSG_ESCALATE_HEADS_UP: escalateHeadsUp(); setHeadsUpVisibility(false); break; } } } /** if the interrupting notification had a fullscreen intent, fire it now. */ private void escalateHeadsUp() { if (mInterruptingNotificationEntry != null) { final StatusBarNotification sbn = mInterruptingNotificationEntry.notification; final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { if (DEBUG) Log.d(TAG, "converting a heads up to fullScreen"); try { notification.fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } } } } public Handler getHandler() { return mHandler; } View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { // Because 'v' is a ViewGroup, all its children will be (un)selected // too, which allows marqueeing to work. v.setSelected(hasFocus); } }; boolean panelsEnabled() { return (mDisabled & StatusBarManager.DISABLE_EXPAND) == 0; } void makeExpandedVisible() { if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); if (mExpandedVisible || !panelsEnabled()) { return; } mExpandedVisible = true; mPile.setLayoutTransitionsEnabled(true); if (mNavigationBarView != null) mNavigationBarView.setSlippery(true); updateCarrierLabelVisibility(true); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); // Expand the window to encompass the full screen in anticipation of the drag. // This is only possible to do atomically because the status bar is at the top of the screen! WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; lp.height = ViewGroup.LayoutParams.MATCH_PARENT; mWindowManager.updateViewLayout(mStatusBarWindow, lp); visibilityChanged(true); setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); } private void releaseFocus() { WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; lp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; mWindowManager.updateViewLayout(mStatusBarWindow, lp); } public void animateCollapsePanels() { animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); } public void animateCollapsePanels(int flags) { if (SPEW) { Log.d(TAG, "animateCollapse():" + " mExpandedVisible=" + mExpandedVisible + " flags=" + flags); } // release focus immediately to kick off focus change transition releaseFocus(); if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); } if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL); } mStatusBarWindow.cancelExpandHelper(); mStatusBarView.collapseAllPanels(true); } public ViewPropertyAnimator setVisibilityWhenDone( final ViewPropertyAnimator a, final View v, final int vis) { a.setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { v.setVisibility(vis); a.setListener(null); // oneshot } }); return a; } public Animator setVisibilityWhenDone( final Animator a, final View v, final int vis) { a.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { v.setVisibility(vis); } }); return a; } public Animator interpolator(TimeInterpolator ti, Animator a) { a.setInterpolator(ti); return a; } public Animator startDelay(int d, Animator a) { a.setStartDelay(d); return a; } public Animator start(Animator a) { a.start(); return a; } final TimeInterpolator mAccelerateInterpolator = new AccelerateInterpolator(); final TimeInterpolator mDecelerateInterpolator = new DecelerateInterpolator(); final int FLIP_DURATION_OUT = 125; final int FLIP_DURATION_IN = 225; final int FLIP_DURATION = (FLIP_DURATION_IN + FLIP_DURATION_OUT); Animator mScrollViewAnim, mFlipSettingsViewAnim, mNotificationButtonAnim, mSettingsButtonAnim, mClearButtonAnim; @Override public void animateExpandNotificationsPanel() { if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible); if (!panelsEnabled()) { return ; } mNotificationPanel.expand(); if (mHasFlipSettings && mScrollView.getVisibility() != View.VISIBLE) { flipToNotifications(); } if (false) postStartTracing(); } public void flipToNotifications() { if (mFlipSettingsViewAnim != null) mFlipSettingsViewAnim.cancel(); if (mScrollViewAnim != null) mScrollViewAnim.cancel(); if (mSettingsButtonAnim != null) mSettingsButtonAnim.cancel(); if (mNotificationButtonAnim != null) mNotificationButtonAnim.cancel(); if (mClearButtonAnim != null) mClearButtonAnim.cancel(); mScrollView.setVisibility(View.VISIBLE); mScrollViewAnim = start( startDelay(FLIP_DURATION_OUT, interpolator(mDecelerateInterpolator, ObjectAnimator.ofFloat(mScrollView, View.SCALE_X, 0f, 1f) .setDuration(FLIP_DURATION_IN) ))); mFlipSettingsViewAnim = start( setVisibilityWhenDone( interpolator(mAccelerateInterpolator, ObjectAnimator.ofFloat(mFlipSettingsView, View.SCALE_X, 1f, 0f) ) .setDuration(FLIP_DURATION_OUT), mFlipSettingsView, View.INVISIBLE)); mNotificationButtonAnim = start( setVisibilityWhenDone( ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 0f) .setDuration(FLIP_DURATION), mNotificationButton, View.INVISIBLE)); mSettingsButton.setVisibility(View.VISIBLE); mSettingsButtonAnim = start( ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 1f) .setDuration(FLIP_DURATION)); mClearButton.setVisibility(View.VISIBLE); mClearButton.setAlpha(0f); setAreThereNotifications(); // this will show/hide the button as necessary mNotificationPanel.postDelayed(new Runnable() { public void run() { updateCarrierLabelVisibility(false); } }, FLIP_DURATION - 150); } @Override public void animateExpandSettingsPanel() { if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible); if (!panelsEnabled()) { return; } // Settings are not available in setup if (!mUserSetup) return; if (mHasFlipSettings) { mNotificationPanel.expand(); if (mFlipSettingsView.getVisibility() != View.VISIBLE) { flipToSettings(); } } else if (mSettingsPanel != null) { mSettingsPanel.expand(); } if (false) postStartTracing(); } public void switchToSettings() { // Settings are not available in setup if (!mUserSetup) return; mFlipSettingsView.setScaleX(1f); mFlipSettingsView.setVisibility(View.VISIBLE); mSettingsButton.setVisibility(View.GONE); mScrollView.setVisibility(View.GONE); mScrollView.setScaleX(0f); mNotificationButton.setVisibility(View.VISIBLE); mNotificationButton.setAlpha(1f); mClearButton.setVisibility(View.GONE); } public void flipToSettings() { // Settings are not available in setup if (!mUserSetup) return; if (mFlipSettingsViewAnim != null) mFlipSettingsViewAnim.cancel(); if (mScrollViewAnim != null) mScrollViewAnim.cancel(); if (mSettingsButtonAnim != null) mSettingsButtonAnim.cancel(); if (mNotificationButtonAnim != null) mNotificationButtonAnim.cancel(); if (mClearButtonAnim != null) mClearButtonAnim.cancel(); mFlipSettingsView.setVisibility(View.VISIBLE); mFlipSettingsView.setScaleX(0f); mFlipSettingsViewAnim = start( startDelay(FLIP_DURATION_OUT, interpolator(mDecelerateInterpolator, ObjectAnimator.ofFloat(mFlipSettingsView, View.SCALE_X, 0f, 1f) .setDuration(FLIP_DURATION_IN) ))); mScrollViewAnim = start( setVisibilityWhenDone( interpolator(mAccelerateInterpolator, ObjectAnimator.ofFloat(mScrollView, View.SCALE_X, 1f, 0f) ) .setDuration(FLIP_DURATION_OUT), mScrollView, View.INVISIBLE)); mSettingsButtonAnim = start( setVisibilityWhenDone( ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 0f) .setDuration(FLIP_DURATION), mScrollView, View.INVISIBLE)); mNotificationButton.setVisibility(View.VISIBLE); mNotificationButtonAnim = start( ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 1f) .setDuration(FLIP_DURATION)); mClearButtonAnim = start( setVisibilityWhenDone( ObjectAnimator.ofFloat(mClearButton, View.ALPHA, 0f) .setDuration(FLIP_DURATION), mClearButton, View.INVISIBLE)); mNotificationPanel.postDelayed(new Runnable() { public void run() { updateCarrierLabelVisibility(false); } }, FLIP_DURATION - 150); } public void flipPanels() { if (mHasFlipSettings) { if (mFlipSettingsView.getVisibility() != View.VISIBLE) { flipToSettings(); } else { flipToNotifications(); } } } public void animateCollapseQuickSettings() { mStatusBarView.collapseAllPanels(true); } void makeExpandedInvisibleSoon() { mHandler.postDelayed(new Runnable() { public void run() { makeExpandedInvisible(); }}, 50); } void makeExpandedInvisible() { if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible + " mExpandedVisible=" + mExpandedVisible); if (!mExpandedVisible) { return; } // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) mStatusBarView.collapseAllPanels(/*animate=*/ false); if (mHasFlipSettings) { // reset things to their proper state if (mFlipSettingsViewAnim != null) mFlipSettingsViewAnim.cancel(); if (mScrollViewAnim != null) mScrollViewAnim.cancel(); if (mSettingsButtonAnim != null) mSettingsButtonAnim.cancel(); if (mNotificationButtonAnim != null) mNotificationButtonAnim.cancel(); if (mClearButtonAnim != null) mClearButtonAnim.cancel(); mScrollView.setScaleX(1f); mScrollView.setVisibility(View.VISIBLE); mSettingsButton.setAlpha(1f); mSettingsButton.setVisibility(View.VISIBLE); mNotificationPanel.setVisibility(View.GONE); mFlipSettingsView.setVisibility(View.GONE); mNotificationButton.setVisibility(View.GONE); setAreThereNotifications(); // show the clear button } mExpandedVisible = false; mPile.setLayoutTransitionsEnabled(false); if (mNavigationBarView != null) mNavigationBarView.setSlippery(false); visibilityChanged(false); // Shrink the window to the size of the status bar only WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); lp.height = getStatusBarHeight(); lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; lp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; mWindowManager.updateViewLayout(mStatusBarWindow, lp); if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); } // Close any "App info" popups that might have snuck on-screen dismissPopups(); if (mPostCollapseCleanup != null) { mPostCollapseCleanup.run(); mPostCollapseCleanup = null; } setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); } /** * Enables or disables layers on the children of the notifications pile. * * When layers are enabled, this method attempts to enable layers for the minimal * number of children. Only children visible when the notification area is fully * expanded will receive a layer. The technique used in this method might cause * more children than necessary to get a layer (at most one extra child with the * current UI.) * * @param layerType {@link View#LAYER_TYPE_NONE} or {@link View#LAYER_TYPE_HARDWARE} */ private void setPileLayers(int layerType) { final int count = mPile.getChildCount(); switch (layerType) { case View.LAYER_TYPE_NONE: for (int i = 0; i < count; i++) { mPile.getChildAt(i).setLayerType(layerType, null); } break; case View.LAYER_TYPE_HARDWARE: final int[] location = new int[2]; mNotificationPanel.getLocationInWindow(location); final int left = location[0]; final int top = location[1]; final int right = left + mNotificationPanel.getWidth(); final int bottom = top + getExpandedViewMaxHeight(); final Rect childBounds = new Rect(); for (int i = 0; i < count; i++) { final View view = mPile.getChildAt(i); view.getLocationInWindow(location); childBounds.set(location[0], location[1], location[0] + view.getWidth(), location[1] + view.getHeight()); if (childBounds.intersects(left, top, right, bottom)) { view.setLayerType(layerType, null); } } break; } } public boolean interceptTouchEvent(MotionEvent event) { if (DEBUG_GESTURES) { if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, event.getActionMasked(), (int) event.getX(), (int) event.getY(), mDisabled); } } if (SPEW) { Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled=" + mDisabled + " mTracking=" + mTracking); } else if (CHATTY) { if (event.getAction() != MotionEvent.ACTION_MOVE) { Log.d(TAG, String.format( "panel: %s at (%f, %f) mDisabled=0x%08x", MotionEvent.actionToString(event.getAction()), event.getRawX(), event.getRawY(), mDisabled)); } } if (DEBUG_GESTURES) { mGestureRec.add(event); } if (mStatusBarWindowState == WINDOW_STATE_SHOWING) { final boolean upOrCancel = event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL; if (upOrCancel && !mExpandedVisible) { setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); } else { setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); } } return false; } public GestureRecorder getGestureRecorder() { return mGestureRec; } private void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; mNavigationIconHints = hints; if (mNavigationBarView != null) { mNavigationBarView.setNavigationIconHints(hints); } checkBarModes(); } @Override // CommandQueue public void setWindowState(int window, int state) { boolean showing = state == WINDOW_STATE_SHOWING; if (mStatusBarWindow != null && window == StatusBarManager.WINDOW_STATUS_BAR && mStatusBarWindowState != state) { mStatusBarWindowState = state; if (DEBUG_WINDOW_STATE) Log.d(TAG, "Status bar " + windowStateToString(state)); if (!showing) { mStatusBarView.collapseAllPanels(false); } } if (mNavigationBarView != null && window == StatusBarManager.WINDOW_NAVIGATION_BAR && mNavigationBarWindowState != state) { mNavigationBarWindowState = state; if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state)); } } @Override // CommandQueue public void setSystemUiVisibility(int vis, int mask) { final int oldVal = mSystemUiVisibility; final int newVal = (oldVal&~mask) | (vis&mask); final int diff = newVal ^ oldVal; if (DEBUG) Log.d(TAG, String.format( "setSystemUiVisibility vis=%s mask=%s oldVal=%s newVal=%s diff=%s", Integer.toHexString(vis), Integer.toHexString(mask), Integer.toHexString(oldVal), Integer.toHexString(newVal), Integer.toHexString(diff))); if (diff != 0) { mSystemUiVisibility = newVal; // update low profile if ((diff & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { final boolean lightsOut = (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0; if (lightsOut) { animateCollapsePanels(); if (mTicking) { haltTicker(); } } setAreThereNotifications(); } // update status bar mode final int sbMode = computeBarMode(oldVal, newVal, mStatusBarView.getBarTransitions(), View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_TRANSLUCENT); // update navigation bar mode final int nbMode = mNavigationBarView == null ? -1 : computeBarMode( oldVal, newVal, mNavigationBarView.getBarTransitions(), View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT); final boolean sbModeChanged = sbMode != -1; final boolean nbModeChanged = nbMode != -1; boolean checkBarModes = false; if (sbModeChanged && sbMode != mStatusBarMode) { mStatusBarMode = sbMode; checkBarModes = true; } if (nbModeChanged && nbMode != mNavigationBarMode) { mNavigationBarMode = nbMode; checkBarModes = true; } if (checkBarModes) { checkBarModes(); } if (sbModeChanged || nbModeChanged) { // update transient bar autohide if (sbMode == MODE_SEMI_TRANSPARENT || nbMode == MODE_SEMI_TRANSPARENT) { scheduleAutohide(); } else { cancelAutohide(); } } // ready to unhide if ((vis & View.STATUS_BAR_UNHIDE) != 0) { mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE; } if ((vis & View.NAVIGATION_BAR_UNHIDE) != 0) { mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE; } // send updated sysui visibility to window manager notifyUiVisibilityChanged(mSystemUiVisibility); } } private int computeBarMode(int oldVis, int newVis, BarTransitions transitions, int transientFlag, int translucentFlag) { final int oldMode = barMode(oldVis, transientFlag, translucentFlag); final int newMode = barMode(newVis, transientFlag, translucentFlag); if (oldMode == newMode) { return -1; // no mode change } return newMode; } private int barMode(int vis, int transientFlag, int translucentFlag) { return (vis & transientFlag) != 0 ? MODE_SEMI_TRANSPARENT : (vis & translucentFlag) != 0 ? MODE_TRANSLUCENT : (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0 ? MODE_LIGHTS_OUT : MODE_OPAQUE; } private void checkBarModes() { if (mDemoMode) return; int sbMode = mStatusBarMode; if (panelsEnabled() && (mInteractingWindows & StatusBarManager.WINDOW_STATUS_BAR) != 0) { // if panels are expandable, force the status bar opaque on any interaction sbMode = MODE_OPAQUE; } checkBarMode(sbMode, mStatusBarWindowState, mStatusBarView.getBarTransitions()); if (mNavigationBarView != null) { checkBarMode(mNavigationBarMode, mNavigationBarWindowState, mNavigationBarView.getBarTransitions()); } } private void checkBarMode(int mode, int windowState, BarTransitions transitions) { final boolean anim = (mScreenOn == null || mScreenOn) && windowState != WINDOW_STATE_HIDDEN; transitions.transitionTo(mode, anim); } private void finishBarAnimations() { mStatusBarView.getBarTransitions().finishAnimations(); if (mNavigationBarView != null) { mNavigationBarView.getBarTransitions().finishAnimations(); } } private final Runnable mCheckBarModes = new Runnable() { @Override public void run() { checkBarModes(); }}; @Override public void setInteracting(int barWindow, boolean interacting) { mInteractingWindows = interacting ? (mInteractingWindows | barWindow) : (mInteractingWindows & ~barWindow); if (mInteractingWindows != 0) { suspendAutohide(); } else { resumeSuspendedAutohide(); } checkBarModes(); } private void resumeSuspendedAutohide() { if (mAutohideSuspended) { scheduleAutohide(); mHandler.postDelayed(mCheckBarModes, 500); // longer than home -> launcher } } private void suspendAutohide() { mHandler.removeCallbacks(mAutohide); mHandler.removeCallbacks(mCheckBarModes); mAutohideSuspended = (mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0; } private void cancelAutohide() { mAutohideSuspended = false; mHandler.removeCallbacks(mAutohide); } private void scheduleAutohide() { cancelAutohide(); mHandler.postDelayed(mAutohide, AUTOHIDE_TIMEOUT_MS); } private void checkUserAutohide(View v, MotionEvent event) { if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0 // a transient bar is revealed && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar && event.getX() == 0 && event.getY() == 0 // a touch outside both bars ) { userAutohide(); } } private void userAutohide() { cancelAutohide(); mHandler.postDelayed(mAutohide, 350); // longer than app gesture -> flag clear } private boolean areLightsOn() { return 0 == (mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE); } public void setLightsOn(boolean on) { Log.v(TAG, "setLightsOn(" + on + ")"); if (on) { setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); } else { setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, View.SYSTEM_UI_FLAG_LOW_PROFILE); } } private void notifyUiVisibilityChanged(int vis) { try { mWindowManagerService.statusBarVisibilityChanged(vis); } catch (RemoteException ex) { } } public void topAppWindowChanged(boolean showMenu) { if (DEBUG) { Log.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); } if (mNavigationBarView != null) { mNavigationBarView.setMenuVisibility(showMenu); } // See above re: lights-out policy for legacy apps. if (showMenu) setLightsOn(true); } @Override public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || ((vis & InputMethodService.IME_VISIBLE) != 0); setNavigationIconHints( altBack ? (mNavigationIconHints | NAVIGATION_HINT_BACK_ALT) : (mNavigationIconHints & ~NAVIGATION_HINT_BACK_ALT)); if (mQS != null) mQS.setImeWindowStatus(vis > 0); } @Override public void setHardKeyboardStatus(boolean available, boolean enabled) {} @Override protected void tick(IBinder key, StatusBarNotification n, boolean firstTime) { // no ticking in lights-out mode if (!areLightsOn()) return; // no ticking in Setup if (!isDeviceProvisioned()) return; // not for you if (!notificationIsForCurrentUser(n)) return; // Show the ticker if one is requested. Also don't do this // until status bar window is attached to the window manager, // because... well, what's the point otherwise? And trying to // run a ticker without being attached will crash! if (n.getNotification().tickerText != null && mStatusBarWindow.getWindowToken() != null) { if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { mTicker.addEntry(n); } } } private class MyTicker extends Ticker { MyTicker(Context context, View sb) { super(context, sb); } @Override public void tickerStarting() { mTicking = true; mStatusBarContents.setVisibility(View.GONE); mTickerView.setVisibility(View.VISIBLE); mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null)); mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null)); } @Override public void tickerDone() { mStatusBarContents.setVisibility(View.VISIBLE); mTickerView.setVisibility(View.GONE); mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null)); mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out, mTickingDoneListener)); } public void tickerHalting() { if (mStatusBarContents.getVisibility() != View.VISIBLE) { mStatusBarContents.setVisibility(View.VISIBLE); mStatusBarContents .startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); } mTickerView.setVisibility(View.GONE); // we do not animate the ticker away at this point, just get rid of it (b/6992707) } } Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {; public void onAnimationEnd(Animation animation) { mTicking = false; } public void onAnimationRepeat(Animation animation) { } public void onAnimationStart(Animation animation) { } }; private Animation loadAnim(int id, Animation.AnimationListener listener) { Animation anim = AnimationUtils.loadAnimation(mContext, id); if (listener != null) { anim.setAnimationListener(listener); } return anim; } public static String viewInfo(View v) { return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() + ") " + v.getWidth() + "x" + v.getHeight() + "]"; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mQueueLock) { pw.println("Current Status Bar state:"); pw.println(" mExpandedVisible=" + mExpandedVisible + ", mTrackingPosition=" + mTrackingPosition); pw.println(" mTicking=" + mTicking); pw.println(" mTracking=" + mTracking); pw.println(" mDisplayMetrics=" + mDisplayMetrics); pw.println(" mPile: " + viewInfo(mPile)); pw.println(" mTickerView: " + viewInfo(mTickerView)); pw.println(" mScrollView: " + viewInfo(mScrollView) + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY()); } pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); pw.print(" mStatusBarWindowState="); pw.println(windowStateToString(mStatusBarWindowState)); pw.print(" mStatusBarMode="); pw.println(BarTransitions.modeToString(mStatusBarMode)); dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); if (mNavigationBarView != null) { pw.print(" mNavigationBarWindowState="); pw.println(windowStateToString(mNavigationBarWindowState)); pw.print(" mNavigationBarMode="); pw.println(BarTransitions.modeToString(mNavigationBarMode)); dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions()); } pw.print(" mNavigationBarView="); if (mNavigationBarView == null) { pw.println("null"); } else { mNavigationBarView.dump(fd, pw, args); } pw.println(" Panels: "); if (mNotificationPanel != null) { pw.println(" mNotificationPanel=" + mNotificationPanel + " params=" + mNotificationPanel.getLayoutParams().debug("")); pw.print (" "); mNotificationPanel.dump(fd, pw, args); } if (mSettingsPanel != null) { pw.println(" mSettingsPanel=" + mSettingsPanel + " params=" + mSettingsPanel.getLayoutParams().debug("")); pw.print (" "); mSettingsPanel.dump(fd, pw, args); } if (DUMPTRUCK) { synchronized (mNotificationData) { int N = mNotificationData.size(); pw.println(" notification icons: " + N); for (int i=0; i<N; i++) { NotificationData.Entry e = mNotificationData.get(i); pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon); StatusBarNotification n = e.notification; pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " score=" + n.getScore()); pw.println(" notification=" + n.getNotification()); pw.println(" tickerText=\"" + n.getNotification().tickerText + "\""); } } int N = mStatusIcons.getChildCount(); pw.println(" system icons: " + N); for (int i=0; i<N; i++) { StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i); pw.println(" [" + i + "] icon=" + ic); } if (false) { pw.println("see the logcat for a dump of the views we have created."); // must happen on ui thread mHandler.post(new Runnable() { public void run() { mStatusBarView.getLocationOnScreen(mAbsPos); Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] + ") " + mStatusBarView.getWidth() + "x" + getStatusBarHeight()); mStatusBarView.debug(); } }); } } if (DEBUG_GESTURES) { pw.print(" status bar gestures: "); mGestureRec.dump(fd, pw, args); } mNetworkController.dump(fd, pw, args); } private static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) { pw.print(" "); pw.print(var); pw.print(".BarTransitions.mMode="); pw.println(BarTransitions.modeToString(transitions.getMode())); } @Override public void createAndAddWindows() { addStatusBarWindow(); } private void addStatusBarWindow() { // Put up the view final int height = getStatusBarHeight(); // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is // hardware-accelerated. final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height, WindowManager.LayoutParams.TYPE_STATUS_BAR, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; lp.gravity = getStatusBarGravity(); lp.setTitle("StatusBar"); lp.packageName = mContext.getPackageName(); makeStatusBarView(); mWindowManager.addView(mStatusBarWindow, lp); } void setNotificationIconVisibility(boolean visible, int anim) { int old = mNotificationIcons.getVisibility(); int v = visible ? View.VISIBLE : View.INVISIBLE; if (old != v) { mNotificationIcons.setVisibility(v); mNotificationIcons.startAnimation(loadAnim(anim, null)); } } void updateExpandedInvisiblePosition() { mTrackingPosition = -mDisplayMetrics.heightPixels; } static final float saturate(float a) { return a < 0f ? 0f : (a > 1f ? 1f : a); } @Override protected int getExpandedViewMaxHeight() { return mDisplayMetrics.heightPixels - mNotificationPanelMarginBottomPx; } @Override public void updateExpandedViewPos(int thingy) { if (SPEW) Log.v(TAG, "updateExpandedViewPos"); // on larger devices, the notification panel is propped open a bit mNotificationPanel.setMinimumHeight( (int)(mNotificationPanelMinHeightFrac * mCurrentDisplaySize.y)); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mNotificationPanel.getLayoutParams(); lp.gravity = mNotificationPanelGravity; lp.setMarginStart(mNotificationPanelMarginPx); mNotificationPanel.setLayoutParams(lp); if (mSettingsPanel != null) { lp = (FrameLayout.LayoutParams) mSettingsPanel.getLayoutParams(); lp.gravity = mSettingsPanelGravity; lp.setMarginEnd(mNotificationPanelMarginPx); mSettingsPanel.setLayoutParams(lp); } if (ENABLE_HEADS_UP && mHeadsUpNotificationView != null) { mHeadsUpNotificationView.setMargin(mNotificationPanelMarginPx); mPile.getLocationOnScreen(mPilePosition); mHeadsUpVerticalOffset = mPilePosition[1] - mNaturalBarHeight; } updateCarrierLabelVisibility(false); } // called by makeStatusbar and also by PhoneStatusBarView void updateDisplaySize() { mDisplay.getMetrics(mDisplayMetrics); mDisplay.getSize(mCurrentDisplaySize); if (DEBUG_GESTURES) { mGestureRec.tag("display", String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } } private View.OnClickListener mClearButtonListener = new View.OnClickListener() { public void onClick(View v) { synchronized (mNotificationData) { // animate-swipe all dismissable notifications, then animate the shade closed int numChildren = mPile.getChildCount(); int scrollTop = mScrollView.getScrollY(); int scrollBottom = scrollTop + mScrollView.getHeight(); final ArrayList<View> snapshot = new ArrayList<View>(numChildren); for (int i=0; i<numChildren; i++) { final View child = mPile.getChildAt(i); if (mPile.canChildBeDismissed(child) && child.getBottom() > scrollTop && child.getTop() < scrollBottom) { snapshot.add(child); } } if (snapshot.isEmpty()) { animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); return; } new Thread(new Runnable() { @Override public void run() { // Decrease the delay for every row we animate to give the sense of // accelerating the swipes final int ROW_DELAY_DECREMENT = 10; int currentDelay = 140; int totalDelay = 0; // Set the shade-animating state to avoid doing other work during // all of these animations. In particular, avoid layout and // redrawing when collapsing the shade. mPile.setViewRemoval(false); mPostCollapseCleanup = new Runnable() { @Override public void run() { if (DEBUG) { Log.v(TAG, "running post-collapse cleanup"); } try { mPile.setViewRemoval(true); mBarService.onClearAllNotifications(); } catch (Exception ex) { } } }; View sampleView = snapshot.get(0); int width = sampleView.getWidth(); final int dir = sampleView.isLayoutRtl() ? -1 : +1; final int velocity = dir * width * 8; // 1000/8 = 125 ms duration for (final View _v : snapshot) { mHandler.postDelayed(new Runnable() { @Override public void run() { mPile.dismissRowAnimated(_v, velocity); } }, totalDelay); currentDelay = Math.max(50, currentDelay - ROW_DELAY_DECREMENT); totalDelay += currentDelay; } // Delay the collapse animation until after all swipe animations have // finished. Provide some buffer because there may be some extra delay // before actually starting each swipe animation. Ideally, we'd // synchronize the end of those animations with the start of the collaps // exactly. mHandler.postDelayed(new Runnable() { @Override public void run() { animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); } }, totalDelay + 225); } }).start(); } } }; public void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned) { if (onlyProvisioned && !isDeviceProvisioned()) return; try { // Dismiss the lock screen when Settings starts. ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); } catch (RemoteException e) { } intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); animateCollapsePanels(); } private View.OnClickListener mSettingsButtonListener = new View.OnClickListener() { public void onClick(View v) { if (mHasSettingsPanel) { animateExpandSettingsPanel(); } else { startActivityDismissingKeyguard( new Intent(android.provider.Settings.ACTION_SETTINGS), true); } } }; private View.OnClickListener mClockClickListener = new View.OnClickListener() { public void onClick(View v) { startActivityDismissingKeyguard( new Intent(Intent.ACTION_QUICK_CLOCK), true); // have fun, everyone } }; private View.OnClickListener mNotificationButtonListener = new View.OnClickListener() { public void onClick(View v) { animateExpandNotificationsPanel(); } }; private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (DEBUG) Log.v(TAG, "onReceive: " + intent); String action = intent.getAction(); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { int flags = CommandQueue.FLAG_EXCLUDE_NONE; if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { String reason = intent.getStringExtra("reason"); if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; } } animateCollapsePanels(flags); } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOn = false; // no waiting! makeExpandedInvisible(); notifyNavigationBarScreenOn(false); notifyHeadsUpScreenOn(false); finishBarAnimations(); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { mScreenOn = true; // work around problem where mDisplay.getRotation() is not stable while screen is off (bug 7086018) repositionNavigationBar(); notifyNavigationBarScreenOn(true); } else if (ACTION_DEMO.equals(action)) { Bundle bundle = intent.getExtras(); if (bundle != null) { String command = bundle.getString("command", "").trim().toLowerCase(); if (command.length() > 0) { try { dispatchDemoCommand(command, bundle); } catch (Throwable t) { Log.w(TAG, "Error running demo command, intent=" + intent, t); } } } } } }; // SystemUIService notifies SystemBars of configuration changes, which then calls down here @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // calls refreshLayout if (DEBUG) { Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration()); } updateDisplaySize(); // populates mDisplayMetrics updateResources(); repositionNavigationBar(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); updateShowSearchHoldoff(); } @Override public void userSwitched(int newUserId) { if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); animateCollapsePanels(); updateNotificationIcons(); resetUserSetupObserver(); } private void resetUserSetupObserver() { mContext.getContentResolver().unregisterContentObserver(mUserSetupObserver); mUserSetupObserver.onChange(false); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), true, mUserSetupObserver, mCurrentUserId); } private void setHeadsUpVisibility(boolean vis) { if (!ENABLE_HEADS_UP) return; if (DEBUG) Log.v(TAG, (vis ? "showing" : "hiding") + " heads up window"); mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE); if (!vis) { if (DEBUG) Log.d(TAG, "setting heads up entry to null"); mInterruptingNotificationEntry = null; } } public void animateHeadsUp(boolean animateInto, float frac) { if (!ENABLE_HEADS_UP || mHeadsUpNotificationView == null) return; frac = frac / 0.4f; frac = frac < 1.0f ? frac : 1.0f; float alpha = 1.0f - frac; float offset = mHeadsUpVerticalOffset * frac; offset = animateInto ? offset : 0f; mHeadsUpNotificationView.setAlpha(alpha); mHeadsUpNotificationView.setY(offset); } public void onHeadsUpDismissed() { if (mInterruptingNotificationEntry == null) return; mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); if (mHeadsUpNotificationView.isClearable()) { try { mBarService.onNotificationClear( mInterruptingNotificationEntry.notification.getPackageName(), mInterruptingNotificationEntry.notification.getTag(), mInterruptingNotificationEntry.notification.getId()); } catch (android.os.RemoteException ex) { // oh well } } } /** * Reload some of our resources when the configuration changes. * * We don't reload everything when the configuration changes -- we probably * should, but getting that smooth is tough. Someday we'll fix that. In the * meantime, just update the things that we know change. */ void updateResources() { final Context context = mContext; final Resources res = context.getResources(); if (mClearButton instanceof TextView) { ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button)); } // Update the QuickSettings container if (mQS != null) mQS.updateResources(); loadDimens(); } protected void loadDimens() { final Resources res = mContext.getResources(); mNaturalBarHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); int newIconSize = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); int newIconHPadding = res.getDimensionPixelSize( R.dimen.status_bar_icon_padding); if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { // Log.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); mIconHPadding = newIconHPadding; mIconSize = newIconSize; //reloadAllNotificationIcons(); // reload the tray } mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity); mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity); mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity); mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity); mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1); mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1); mExpandAccelPx = res.getDimension(R.dimen.expand_accel); mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel); mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity); mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity); mNotificationPanelMarginBottomPx = (int) res.getDimension(R.dimen.notification_panel_margin_bottom); mNotificationPanelMarginPx = (int) res.getDimension(R.dimen.notification_panel_margin_left); mNotificationPanelGravity = res.getInteger(R.integer.notification_panel_layout_gravity); if (mNotificationPanelGravity <= 0) { mNotificationPanelGravity = Gravity.START | Gravity.TOP; } mSettingsPanelGravity = res.getInteger(R.integer.settings_panel_layout_gravity); Log.d(TAG, "mSettingsPanelGravity = " + mSettingsPanelGravity); if (mSettingsPanelGravity <= 0) { mSettingsPanelGravity = Gravity.END | Gravity.TOP; } mCarrierLabelHeight = res.getDimensionPixelSize(R.dimen.carrier_label_height); mNotificationHeaderHeight = res.getDimensionPixelSize(R.dimen.notification_panel_header_height); mNotificationPanelMinHeightFrac = res.getFraction(R.dimen.notification_panel_min_height_frac, 1, 1); if (mNotificationPanelMinHeightFrac < 0f || mNotificationPanelMinHeightFrac > 1f) { mNotificationPanelMinHeightFrac = 0f; } mHeadsUpNotificationDecay = res.getInteger(R.integer.heads_up_notification_decay); mRowHeight = res.getDimensionPixelSize(R.dimen.notification_row_min_height); if (false) Log.v(TAG, "updateResources"); } // // tracing // void postStartTracing() { mHandler.postDelayed(mStartTracing, 3000); } void vibrate() { android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService( Context.VIBRATOR_SERVICE); vib.vibrate(250); } Runnable mStartTracing = new Runnable() { public void run() { vibrate(); SystemClock.sleep(250); Log.d(TAG, "startTracing"); android.os.Debug.startMethodTracing("/data/statusbar-traces/trace"); mHandler.postDelayed(mStopTracing, 10000); } }; Runnable mStopTracing = new Runnable() { public void run() { android.os.Debug.stopMethodTracing(); Log.d(TAG, "stopTracing"); vibrate(); } }; @Override protected void haltTicker() { mTicker.halt(); } @Override protected boolean shouldDisableNavbarGestures() { return !isDeviceProvisioned() || mExpandedVisible || (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0; } private static class FastColorDrawable extends Drawable { private final int mColor; public FastColorDrawable(int color) { mColor = 0xff000000 | color; } @Override public void draw(Canvas canvas) { canvas.drawColor(mColor, PorterDuff.Mode.SRC); } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter cf) { } @Override public int getOpacity() { return PixelFormat.OPAQUE; } @Override public void setBounds(int left, int top, int right, int bottom) { } @Override public void setBounds(Rect bounds) { } } @Override public void destroy() { super.destroy(); if (mStatusBarWindow != null) { mWindowManager.removeViewImmediate(mStatusBarWindow); } if (mNavigationBarView != null) { mWindowManager.removeViewImmediate(mNavigationBarView); } mContext.unregisterReceiver(mBroadcastReceiver); } private boolean mDemoModeAllowed; private boolean mDemoMode; private DemoStatusIcons mDemoStatusIcons; @Override public void dispatchDemoCommand(String command, Bundle args) { if (!mDemoModeAllowed) { mDemoModeAllowed = Settings.Global.getInt(mContext.getContentResolver(), "sysui_demo_allowed", 0) != 0; } if (!mDemoModeAllowed) return; if (command.equals(COMMAND_ENTER)) { mDemoMode = true; } else if (command.equals(COMMAND_EXIT)) { mDemoMode = false; checkBarModes(); } else if (!mDemoMode) { // automatically enter demo mode on first demo command dispatchDemoCommand(COMMAND_ENTER, new Bundle()); } boolean modeChange = command.equals(COMMAND_ENTER) || command.equals(COMMAND_EXIT); if (modeChange || command.equals(COMMAND_CLOCK)) { dispatchDemoCommandToView(command, args, R.id.clock); } if (modeChange || command.equals(COMMAND_BATTERY)) { dispatchDemoCommandToView(command, args, R.id.battery); } if (modeChange || command.equals(COMMAND_STATUS)) { if (mDemoStatusIcons == null) { mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize); } mDemoStatusIcons.dispatchDemoCommand(command, args); } if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) { mNetworkController.dispatchDemoCommand(command, args); } if (command.equals(COMMAND_BARS)) { String mode = args.getString("mode"); int barMode = "opaque".equals(mode) ? MODE_OPAQUE : "translucent".equals(mode) ? MODE_TRANSLUCENT : "semi-transparent".equals(mode) ? MODE_SEMI_TRANSPARENT : -1; if (barMode != -1) { boolean animate = true; if (mStatusBarView != null) { mStatusBarView.getBarTransitions().transitionTo(barMode, animate); } if (mNavigationBarView != null) { mNavigationBarView.getBarTransitions().transitionTo(barMode, animate); } } } } private void dispatchDemoCommandToView(String command, Bundle args, int id) { if (mStatusBarView == null) return; View v = mStatusBarView.findViewById(id); if (v instanceof DemoMode) { ((DemoMode)v).dispatchDemoCommand(command, args); } } }