/* * 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_LIGHTS_OUT; 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.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; 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.media.AudioManager; 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.provider.Settings.Global; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; 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.TextView; import com.android.internal.statusbar.StatusBarIcon; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.DemoMode; import com.android.systemui.EventLogTags; import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.InterceptedNotifications; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.NotificationOverflowContainer; 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.NetworkController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; 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; /** The minimum delay in ms between reports of notification visibility. */ private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; // 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; NetworkController mNetworkController; RotationLockController mRotationLockController; int mNaturalBarHeight = -1; int mIconSize = -1; int mIconHPadding = -1; Display mDisplay; Point mCurrentDisplaySize = new Point(); private float mHeadsUpVerticalOffset; private int[] mStackScrollerPosition = new int[2]; StatusBarWindowView mStatusBarWindow; PhoneStatusBarView mStatusBarView; private int mStatusBarWindowState = WINDOW_STATE_SHOWING; private StatusBarWindowManager mStatusBarWindowManager; 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; // mode indicator icon ImageView mModeIcon; // expanded notifications NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window 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 mKeyguardStatusView; int mKeyguardMaxNotificationCount; View mDateTimeView; View mClearButton; FlipperButton mHeaderFlipper, mKeyguardFlipper; // 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)); mHeaderFlipper.userSetup(userSetup); mKeyguardFlipper.userSetup(userSetup); 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 && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, Settings.Global.HEADS_UP_OFF); mHeadsUpTicker = mUseHeadsUp && 0 != Settings.Global.getInt( mContext.getContentResolver(), SETTING_HEADS_UP_TICKER, 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"); setHeadsUpVisibility(false); mHeadsUpNotificationView.setNotification(null); removeHeadsUpView(); } else { addHeadsUpView(); } } } }; private int mInteractingWindows; private boolean mAutohideSuspended; private int mStatusBarMode; private int mNavigationBarMode; private Boolean mScreenOn; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private ViewMediatorCallback mKeyguardViewMediatorCallback; private final Runnable mAutohide = new Runnable() { @Override public void run() { int requested = mSystemUiVisibility & ~STATUS_OR_NAV_TRANSIENT; if (mSystemUiVisibility != requested) { notifyUiVisibilityChanged(requested); } }}; private Runnable mOnFlipRunnable; private InterceptedNotifications mIntercepted; private VelocityTracker mSettingsTracker; private float mSettingsDownY; private boolean mSettingsCancelled; private boolean mSettingsClosing; private int mNotificationPadding; private final OnChildLocationsChangedListener mOnChildLocationsChangedListener = new OnChildLocationsChangedListener() { @Override public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) { userActivity(); } }; public void setOnFlipRunnable(Runnable onFlipRunnable) { mOnFlipRunnable = onFlipRunnable; } /** Keys of notifications currently visible to the user. */ private final ArraySet mCurrentlyVisibleNotifications = new ArraySet(); private long mLastVisibilityReportUptimeMs; private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD | ViewState.LOCATION_TOP_STACK_PEEKING | ViewState.LOCATION_MAIN_AREA | ViewState.LOCATION_BOTTOM_STACK_PEEKING; private final OnChildLocationsChangedListener mNotificationLocationsChangedListener = new OnChildLocationsChangedListener() { @Override public void onChildLocationsChanged( NotificationStackScrollLayout stackScrollLayout) { if (mHandler.hasCallbacks(mVisibilityReporter)) { // Visibilities will be reported when the existing // callback is executed. return; } // Calculate when we're allowed to run the visibility // reporter. Note that this timestamp might already have // passed. That's OK, the callback will just be executed // ASAP. long nextReportUptimeMs = mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS; mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs); } }; // Tracks notifications currently visible in mNotificationStackScroller and // emits visibility events via NoMan on changes. private final Runnable mVisibilityReporter = new Runnable() { private final ArrayList mTmpNewlyVisibleNotifications = new ArrayList(); private final ArrayList mTmpCurrentlyVisibleNotifications = new ArrayList(); @Override public void run() { mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); // 1. Loop over mNotificationData entries: // A. Keep list of visible notifications. // B. Keep list of previously hidden, now visible notifications. // 2. Compute no-longer visible notifications by removing currently // visible notifications from the set of previously visible // notifications. // 3. Report newly visible and no-longer visible notifications. // 4. Keep currently visible notifications for next report. int N = mNotificationData.size(); for (int i = 0; i < N; i++) { Entry entry = mNotificationData.get(i); String key = entry.notification.getKey(); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(key); boolean currentlyVisible = (mStackScroller.getChildLocation(entry.row) & VISIBLE_LOCATIONS) != 0; if (currentlyVisible) { // Build new set of visible notifications. mTmpCurrentlyVisibleNotifications.add(key); } if (!previouslyVisible && currentlyVisible) { mTmpNewlyVisibleNotifications.add(key); } } ArraySet noLongerVisibleNotifications = mCurrentlyVisibleNotifications; noLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); logNotificationVisibilityChanges( mTmpNewlyVisibleNotifications, noLongerVisibleNotifications); mCurrentlyVisibleNotifications.clear(); mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); mTmpNewlyVisibleNotifications.clear(); mTmpCurrentlyVisibleNotifications.clear(); } }; @Override public void setZenMode(int mode) { super.setZenMode(mode); if (mModeIcon == null) return; if (!isDeviceProvisioned()) return; final boolean zen = mode != Settings.Global.ZEN_MODE_OFF; mModeIcon.setVisibility(zen ? View.VISIBLE : View.GONE); if (!zen) { mIntercepted.releaseIntercepted(); } } @Override public void start() { mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); updateDisplaySize(); mIntercepted = new InterceptedNotifications(mContext, this); super.start(); // calls createAndAddWindows() addNavigationBar(); // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext); mSettingsObserver.onChange(false); // set up mHeadsUpObserver.onChange(true); // set up if (ENABLE_HEADS_UP) { mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true, mHeadsUpObserver); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, mHeadsUpObserver); } startKeyguard(); } // ================================================================================ // 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); mModeIcon = (ImageView)mStatusBarView.findViewById(R.id.modeIcon); mModeIcon.setImageResource(R.drawable.stat_sys_zen_limited); mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents); mTickerView = mStatusBarView.findViewById(R.id.ticker); mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById( R.id.notification_stack_scroller); mStackScroller.setLongPressListener(getNotificationLongClicker()); mStackScroller.setChildLocationsChangedListener(mOnChildLocationsChangedListener); mKeyguardIconOverflowContainer = (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false); mKeyguardIconOverflowContainer.setOnActivatedListener(this); mStackScroller.addView(mKeyguardIconOverflowContainer); mExpandedContents = mStackScroller; mNotificationPanelHeader = mStatusBarWindow.findViewById(R.id.header); mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view); 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); } mHeaderFlipper = new FlipperButton(mNotificationPanelHeader .findViewById(R.id.settings_button_holder)); ViewStub flipStub = (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_flip_stub); mKeyguardFlipper = new FlipperButton(flipStub.inflate()); if (!mNotificationPanelIsFullScreenWidth) { mNotificationPanel.setSystemUiVisibility( 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); mNetworkController = new NetworkController(mContext); mBluetoothController = new BluetoothController(mContext); if (mContext.getResources().getBoolean(R.bool.config_showRotationLock) || QuickSettings.DEBUG_GONE_TILES) { 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); // TODO: Uncomment when correctly positioned // 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 // TODO: uncomment, handle this for the Stack scroller aswell // ((NotificationRowLayout) mStackScroller) // .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) mNotificationPadding = mContext.getResources() .getDimensionPixelSize(R.dimen.notification_side_padding); 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_ICONS | 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); } 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; } public boolean onSettingsEvent(MotionEvent event) { userActivity(); if (mSettingsClosing && mFlipSettingsViewAnim != null && mFlipSettingsViewAnim.isRunning()) { return true; } if (mSettingsTracker != null) { mSettingsTracker.addMovement(event); } if (event.getAction() == MotionEvent.ACTION_DOWN) { mSettingsTracker = VelocityTracker.obtain(); mSettingsDownY = event.getY(); mSettingsCancelled = false; mSettingsClosing = mFlipSettingsView.getVisibility() == View.VISIBLE; mFlipSettingsView.setVisibility(View.VISIBLE); mStackScroller.setVisibility(View.VISIBLE); positionSettings(0); if (!mSettingsClosing) { mFlipSettingsView.setTranslationY(-mNotificationPanel.getMeasuredHeight()); } dispatchSettingsEvent(event); } else if (mSettingsTracker != null && (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL)) { final float dy = event.getY() - mSettingsDownY; final FlipperButton flipper = mOnKeyguard ? mKeyguardFlipper : mHeaderFlipper; final boolean inButton = flipper.inHolderBounds(event); final int slop = ViewConfiguration.get(mContext).getScaledTouchSlop(); final boolean qsTap = mSettingsClosing && Math.abs(dy) < slop; if (!qsTap && !inButton) { mSettingsTracker.computeCurrentVelocity(1000); final float vy = mSettingsTracker.getYVelocity(); if (dy <= slop || vy <= 0) { flipToNotifications(); } else { flipToSettings(); } } mSettingsTracker.recycle(); mSettingsTracker = null; dispatchSettingsEvent(event); } else if (mSettingsTracker != null && event.getAction() == MotionEvent.ACTION_MOVE) { final float dy = event.getY() - mSettingsDownY; positionSettings(dy); if (mSettingsClosing) { final boolean qsTap = Math.abs(dy) < ViewConfiguration.get(mContext).getScaledTouchSlop(); if (!mSettingsCancelled && !qsTap) { MotionEvent cancelEvent = MotionEvent.obtainNoHistory(event); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); dispatchSettingsEvent(cancelEvent); mSettingsCancelled = true; } } else { dispatchSettingsEvent(event); } } return true; } private void dispatchSettingsEvent(MotionEvent event) { final View target = mSettingsClosing ? mFlipSettingsView : mNotificationPanelHeader; final int[] targetLoc = new int[2]; target.getLocationInWindow(targetLoc); final int[] panelLoc = new int[2]; mNotificationPanel.getLocationInWindow(panelLoc); final int dx = targetLoc[0] - panelLoc[0]; final int dy = targetLoc[1] - panelLoc[1]; event.offsetLocation(-dx, -dy); target.dispatchTouchEvent(event); } private void positionSettings(float dy) { if (mSettingsClosing) { final int ph = mNotificationPanel.getMeasuredHeight(); dy = Math.min(Math.max(-ph, dy), 0); mFlipSettingsView.setTranslationY(dy); mStackScroller.setTranslationY(ph + dy); } else { final int h = mFlipSettingsView.getBottom(); dy = Math.min(Math.max(0, dy), h); mFlipSettingsView.setTranslationY(-h + dy); mStackScroller.setTranslationY(dy); } } private void startKeyguard() { KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class); mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, mStatusBarWindow, mStatusBarWindowManager); mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); } @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); } } 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.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 UserHandle getCurrentUserHandle() { return new UserHandle(mCurrentUserId); } 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 (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(key, notification)) { return; } if (mUseHeadsUp && shouldInterrupt(notification)) { if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); Entry interruptionCandidate = new Entry(key, notification, null); ViewGroup holder = mHeadsUpNotificationView.getHolder(); if (inflateViewsForHeadsUp(interruptionCandidate, holder)) { 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() { mHandler.removeMessages(MSG_HIDE_HEADS_UP); if (mUseHeadsUp && mHeadsUpNotificationDecay > 0 && mHeadsUpNotificationView.isClearable()) { mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, mHeadsUpNotificationDecay); } } @Override public void updateNotification(IBinder key, StatusBarNotification notification) { super.updateNotification(key, notification); mIntercepted.update(key, notification); } 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() && !mOnKeyguard) { animateCollapsePanels(); } } mIntercepted.remove(key); 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); } mHeaderFlipper.refreshLayout(); mKeyguardFlipper.refreshLayout(); refreshAllStatusBarIcons(); } private void updateShowSearchHoldoff() { mShowSearchHoldoff = mContext.getResources().getInteger( R.integer.config_show_search_delay); } private void loadNotificationShade() { if (mStackScroller == null) return; int N = mNotificationData.size(); ArrayList toShow = new ArrayList(); final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i toRemove = new ArrayList(); for (int i=0; i< mStackScroller.getChildCount(); i++) { View child = mStackScroller.getChildAt(i); if (!toShow.contains(child) && child != mKeyguardIconOverflowContainer) { toRemove.add(child); } } for (View remove : toRemove) { mStackScroller.removeView(remove); } for (int i=0; i