/* * 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.NAVIGATION_HINT_IME_SHOWN; 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.keyguard.KeyguardHostView.OnDismissAction; 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.phone.BarTransitions.MODE_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.IActivityManager; 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.Bitmap; 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.ColorDrawable; import android.graphics.drawable.Drawable; import android.inputmethodservice.InputMethodService; import android.media.AudioAttributes; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.AsyncTask; 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.Trace; import android.os.UserHandle; import android.provider.Settings; import android.service.notification.NotificationListenerService.RankingMap; 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.HardwareCanvas; import android.view.KeyEvent; import android.view.LayoutInflater; 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.ViewTreeObserver; 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.view.animation.Interpolator; import android.view.animation.PathInterpolator; 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.doze.DozeService; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QSTile; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyguardIndicationController; 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.SpeedBumpView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.CastControllerImpl; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.HeadsUpNotificationView; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.HotspotControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.LocationControllerImpl; import com.android.systemui.statusbar.policy.NetworkControllerImpl; import com.android.systemui.statusbar.policy.RotationLockControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; import com.android.systemui.statusbar.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.OnDragDownListener, ActivityStarter { 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_MEDIA = false; public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false; public static final boolean DEBUG_WINDOW_STATE = false; // 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"; public static final boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true; 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; /** * The delay to reset the hint text when the hint animation is finished running. */ private static final int HINT_RESET_DELAY_MS = 1200; private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build(); public static final int FADE_KEYGUARD_START_DELAY = 100; public static final int FADE_KEYGUARD_DURATION = 300; PhoneStatusBarPolicy mIconPolicy; // These are no longer handled by the policy, because we need custom strategies for them BluetoothControllerImpl mBluetoothController; BatteryController mBatteryController; LocationControllerImpl mLocationController; NetworkControllerImpl mNetworkController; HotspotControllerImpl mHotspotController; RotationLockControllerImpl mRotationLockController; UserInfoController mUserInfoController; ZenModeController mZenModeController; CastControllerImpl mCastController; VolumeComponent mVolumeComponent; KeyguardUserSwitcher mKeyguardUserSwitcher; FlashlightController mFlashlightController; UserSwitcherController mUserSwitcherController; KeyguardMonitor mKeyguardMonitor; int mNaturalBarHeight = -1; int mIconSize = -1; int mIconHPadding = -1; Display mDisplay; Point mCurrentDisplaySize = new Point(); StatusBarWindowView mStatusBarWindow; PhoneStatusBarView mStatusBarView; private int mStatusBarWindowState = WINDOW_STATE_SHOWING; private StatusBarWindowManager mStatusBarWindowManager; private UnlockMethodCache mUnlockMethodCache; private DozeServiceHost mDozeServiceHost; int mPixelFormat; Object mQueueLock = new Object(); // viewgroup containing the normal contents of the statusbar LinearLayout mStatusBarContents; // right-hand icons LinearLayout mSystemIconArea; LinearLayout mSystemIcons; // left-hand icons LinearLayout mStatusIcons; // the icons themselves IconMerger mNotificationIcons; View mNotificationIconArea; // [+> View mMoreIcon; // expanded notifications NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window View mExpandedContents; int mNotificationPanelGravity; int mNotificationPanelMarginBottomPx; float mNotificationPanelMinHeightFrac; TextView mNotificationPanelDebugText; // settings View mFlipSettingsView; private QSPanel mQSPanel; // top bar StatusBarHeaderView mHeader; View mKeyguardStatusView; KeyguardBottomAreaView mKeyguardBottomArea; boolean mLeaveOpenOnKeyguardHide; KeyguardIndicationController mKeyguardIndicationController; private boolean mKeyguardFadingAway; private long mKeyguardFadingAwayDelay; private long mKeyguardFadingAwayDuration; int mKeyguardMaxNotificationCount; View mDateTimeView; // carrier/wifi label private TextView mCarrierLabel; private boolean mCarrierLabelVisible = false; private int mCarrierLabelHeight; private int mStatusBarHeaderHeight; private boolean mShowCarrierInPanel = false; // position int[] mPositionTmp = new int[2]; boolean mExpandedVisible; // 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 boolean mTickerEnabled; 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; // 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 (userSetup != mUserSetup) { mUserSetup = userSetup; if (mNotificationPanel != null) { mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned() && 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.release(); 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 ScrimController mScrimController; private final Runnable mAutohide = new Runnable() { @Override public void run() { int requested = mSystemUiVisibility & ~STATUS_OR_NAV_TRANSIENT; if (mSystemUiVisibility != requested) { notifyUiVisibilityChanged(requested); } }}; private boolean mVisible; private boolean mWaitingForKeyguardExit; private boolean mDozing; private Interpolator mLinearOutSlowIn; private Interpolator mAlphaIn = new PathInterpolator(0f, 0.2f, 1f, 1f); private Interpolator mAlphaOut = new PathInterpolator(0f, 0f, 0.8f, 1f); private FrameLayout mBackdrop; private ImageView mBackdropFront, mBackdropBack; private MediaSessionManager mMediaSessionManager; private MediaController mMediaController; private String mMediaNotificationKey; private MediaMetadata mMediaMetadata; private MediaController.Callback mMediaListener = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { super.onPlaybackStateChanged(state); if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state); } @Override public void onMetadataChanged(MediaMetadata metadata) { super.onMetadataChanged(metadata); if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); mMediaMetadata = metadata; updateMediaMetaData(true); } }; private final OnChildLocationsChangedListener mOnChildLocationsChangedListener = new OnChildLocationsChangedListener() { @Override public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) { userActivity(); } }; private int mDisabledUnmodified; /** Keys of notifications currently visible to the user. */ private final ArraySet mCurrentlyVisibleNotifications = new ArraySet(); private long mLastVisibilityReportUptimeMs; private final ShadeUpdates mShadeUpdates = new ShadeUpdates(); private int mDrawCount; private Runnable mLaunchTransitionEndRunnable; private boolean mLaunchTransitionFadingAway; 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(); } }; private final View.OnClickListener mOverflowClickListener = new View.OnClickListener() { @Override public void onClick(View v) { goToLockedShade(null); } }; @Override protected void setShowLockscreenNotifications(boolean show) { super.setShowLockscreenNotifications(show); updateStackScrollerState(); } @Override public void start() { mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); updateDisplaySize(); super.start(); // calls createAndAddWindows() mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); // TODO: use MediaSessionManager.SessionListener to hook us up to future updates // in session state 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); } mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); startKeyguard(); mDozeServiceHost = new DozeServiceHost(); putComponent(DozeService.Host.class, mDozeServiceHost); setControllerUsers(); } // ================================================================================ // Constructing the view // ================================================================================ protected PhoneStatusBarView makeStatusBarView() { final Context context = mContext; Resources res = context.getResources(); updateDisplaySize(); // populates mDisplayMetrics updateResources(); 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); 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); mSystemIcons = (LinearLayout) mStatusBarView.findViewById(R.id.system_icons); mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons); mNotificationIconArea = mStatusBarView.findViewById(R.id.notification_icon_area_inner); 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); mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById( R.id.notification_stack_scroller); mStackScroller.setLongPressListener(getNotificationLongClicker()); mKeyguardIconOverflowContainer = (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false); mKeyguardIconOverflowContainer.setOnActivatedListener(this); mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener); mStackScroller.addView(mKeyguardIconOverflowContainer); SpeedBumpView speedBump = (SpeedBumpView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_speed_bump, mStackScroller, false); mStackScroller.setSpeedBumpView(speedBump); mExpandedContents = mStackScroller; mScrimController = new ScrimController(mStatusBarWindow.findViewById(R.id.scrim_behind), mStatusBarWindow.findViewById(R.id.scrim_in_front)); mStatusBarView.setScrimController(mScrimController); mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header); mHeader.setActivityStarter(this); mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view); mKeyguardBottomArea = (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area); mKeyguardBottomArea.setActivityStarter(this); mKeyguardIndicationController = new KeyguardIndicationController(mContext, (KeyguardIndicationTextView) mStatusBarWindow.findViewById( R.id.keyguard_indication_text)); mDateTimeView = mHeader.findViewById(R.id.datetime); if (mDateTimeView != null) { mDateTimeView.setOnClickListener(mClockClickListener); mDateTimeView.setEnabled(true); } mTickerEnabled = res.getBoolean(R.bool.enable_ticker); if (mTickerEnabled) { final ViewStub tickerStub = (ViewStub) mStatusBarView.findViewById(R.id.ticker_stub); if (tickerStub != null) { mTickerView = tickerStub.inflate(); 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 LocationControllerImpl(mContext); // will post a notification mBatteryController = new BatteryController(mContext); mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() { @Override public void onPowerSaveChanged() { mHandler.post(mCheckBarModes); } @Override public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { // noop } }); mNetworkController = new NetworkControllerImpl(mContext); mHotspotController = new HotspotControllerImpl(mContext); mBluetoothController = new BluetoothControllerImpl(mContext); if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) { mRotationLockController = new RotationLockControllerImpl(mContext); } mUserInfoController = new UserInfoController(mContext); mVolumeComponent = getComponent(VolumeComponent.class); mZenModeController = mVolumeComponent.getZenController(); mCastController = new CastControllerImpl(mContext); final SignalClusterView signalCluster = (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster); mNetworkController.addSignalCluster(signalCluster); signalCluster.setNetworkController(mNetworkController); final boolean isAPhone = mNetworkController.hasVoiceCallingFeature(); if (isAPhone) { mNetworkController.addEmergencyLabelView(mHeader); } 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); } mFlashlightController = new FlashlightController(mContext); mKeyguardBottomArea.setFlashlightController(mFlashlightController); mUserSwitcherController = new UserSwitcherController(mContext); mKeyguardMonitor = new KeyguardMonitor(); mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext, (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mHeader, mUserSwitcherController); // Set up the quick settings tile panel mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel); if (mQSPanel != null) { final QSTileHost qsh = new QSTileHost(mContext, this, mBluetoothController, mLocationController, mRotationLockController, mNetworkController, mZenModeController, mHotspotController, mCastController, mFlashlightController, mUserSwitcherController, mKeyguardMonitor); mQSPanel.setHost(qsh); for (QSTile tile : qsh.getTiles()) { mQSPanel.addTile(tile); } mHeader.setQSPanel(mQSPanel); } mBackdrop = (FrameLayout) mStatusBarWindow.findViewById(R.id.backdrop); mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front); mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back); // User info. Trigger first load. mHeader.setUserInfoController(mUserInfoController); mUserInfoController.reloadUserInfo(); mHeader.setBatteryController(mBatteryController); 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); if (DEBUG_MEDIA_FAKE_ARTWORK) { filter.addAction("fake_artwork"); } filter.addAction(ACTION_DEMO); context.registerReceiver(mBroadcastReceiver, filter); // listen for USER_SETUP_COMPLETE setting (per-user) resetUserSetupObserver(); startGlyphRasterizeHack(); return mStatusBarView; } /** * Hack to improve glyph rasterization for scaled text views. */ private void startGlyphRasterizeHack() { mStatusBarView.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (mDrawCount == 1) { mStatusBarView.getViewTreeObserver().removeOnPreDrawListener(this); HardwareCanvas.setProperty("extraRasterBucket", Float.toString(StackScrollAlgorithm.DIMMED_SCALE)); HardwareCanvas.setProperty("extraRasterBucket", Float.toString( mContext.getResources().getDimensionPixelSize( R.dimen.qs_time_collapsed_size) / mContext.getResources().getDimensionPixelSize( R.dimen.qs_time_expanded_size))); } mDrawCount++; return true; } }); } @Override protected void setZenMode(int mode) { super.setZenMode(mode); if (mIconPolicy != null) { mIconPolicy.setZenMode(mode); } } private void startKeyguard() { KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class); mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, mStatusBarWindow, mStatusBarWindowManager, mScrimController); 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; } public StatusBarWindowView getStatusBarWindow() { return mStatusBarWindow; } @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 View.OnLongClickListener mLockToAppClickListener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { toggleLockedApp(); return true; } }; private int mShowSearchHoldoff = 0; private Runnable mShowSearchPanel = new Runnable() { public void run() { showSearchPanel(); awakenDreams(); } }; View.OnTouchListener mHomeActionListener = 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.getRecentsButton().setLongClickable(true); mNavigationBarView.getRecentsButton().setOnLongClickListener(mLockToAppClickListener); mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener); 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() { int headsUpHeight = mContext.getResources() .getDimensionPixelSize(R.dimen.heads_up_window_height); WindowManager.LayoutParams lp = new WindowManager.LayoutParams( LayoutParams.MATCH_PARENT, headsUpHeight, 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); } @Override public void addNotification(StatusBarNotification notification, RankingMap ranking) { if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey()); if (mUseHeadsUp && shouldInterrupt(notification)) { if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); Entry interruptionCandidate = new Entry(notification, null); ViewGroup holder = mHeadsUpNotificationView.getHolder(); if (inflateViewsForHeadsUp(interruptionCandidate, holder)) { // 1. Populate mHeadsUpNotificationView mHeadsUpNotificationView.showNotification(interruptionCandidate); // do not show the notification in the shade, yet. return; } } Entry shadeEntry = createNotificationViews(notification); if (shadeEntry == null) { return; } 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 (mHeadsUpNotificationView.getEntry() == null) { tick(notification, true); } } addNotificationViews(shadeEntry, ranking); // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); } public void displayNotificationFromHeadsUp(StatusBarNotification notification) { NotificationData.Entry shadeEntry = createNotificationViews(notification); if (shadeEntry == null) { return; } shadeEntry.setInterruption(); addNotificationViews(shadeEntry, null); // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); } @Override public void resetHeadsUpDecayTimer() { mHandler.removeMessages(MSG_DECAY_HEADS_UP); if (mUseHeadsUp && mHeadsUpNotificationDecay > 0 && mHeadsUpNotificationView.isClearable()) { mHandler.sendEmptyMessageDelayed(MSG_DECAY_HEADS_UP, mHeadsUpNotificationDecay); } } @Override public void scheduleHeadsUpOpen() { mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); } @Override public void scheduleHeadsUpClose() { mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); } @Override public void scheduleHeadsUpEscalation() { mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); } @Override protected void updateNotificationRanking(RankingMap ranking) { mNotificationData.updateRanking(ranking); updateNotifications(); } @Override public void removeNotification(String key, RankingMap ranking) { if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null && key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) { mHeadsUpNotificationView.clear(); } StatusBarNotification old = removeNotificationViews(key, ranking); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { // Cancel the ticker if it's still running if (mTickerEnabled) { mTicker.removeEntry(old); } // Recalculate the position of the sliding windows and the titles. updateExpandedViewPos(EXPANDED_LEAVE_ALONE); if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0 && !mNotificationPanel.isTracking() && mState != StatusBarState.KEYGUARD) { animateCollapsePanels(); } } setAreThereNotifications(); } @Override protected void refreshLayout(int layoutDirection) { if (mNavigationBarView != null) { mNavigationBarView.setLayoutDirection(layoutDirection); } refreshAllStatusBarIcons(); } private void updateShowSearchHoldoff() { mShowSearchHoldoff = mContext.getResources().getInteger( R.integer.config_show_search_delay); } private void updateNotificationShade() { 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 instanceof ExpandableNotificationRow) { toRemove.add(child); } } for (View remove : toRemove) { mStackScroller.removeView(remove); } for (int i=0; i