/* * 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.tablet; import android.animation.LayoutTransition; import android.animation.ObjectAnimator; 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.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.text.TextUtils; import android.util.Slog; import android.view.Display; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; 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.internal.statusbar.StatusBarNotification; import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DoNotDisturb; 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.CompatModeButton; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NotificationRowLayout; import com.android.systemui.statusbar.policy.Prefs; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; public class TabletStatusBar extends BaseStatusBar implements InputMethodsPanel.OnHardKeyboardEnabledChangeListener { public static final boolean DEBUG = false; public static final boolean DEBUG_COMPAT_HELP = false; public static final String TAG = "TabletStatusBar"; public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002; public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003; // 1020-1029 reserved for BaseStatusBar public static final int MSG_SHOW_CHROME = 1030; public static final int MSG_HIDE_CHROME = 1031; public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040; public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041; public static final int MSG_OPEN_COMPAT_MODE_PANEL = 1050; public static final int MSG_CLOSE_COMPAT_MODE_PANEL = 1051; public static final int MSG_STOP_TICKER = 2000; // Fitts' Law assistance for LatinIME; see policy.EventHole private static final boolean FAKE_SPACE_BAR = true; // Notification "peeking" (flyover preview of individual notifications) final static int NOTIFICATION_PEEK_HOLD_THRESH = 200; // ms final static int NOTIFICATION_PEEK_FADE_DELAY = 3000; // ms private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER; // The height of the bar, as definied by the build. It may be taller if we're plugged // into hdmi. int mNaturalBarHeight = -1; int mIconSize = -1; int mIconHPadding = -1; int mNavIconWidth = -1; int mMenuNavIconWidth = -1; private int mMaxNotificationIcons = 5; TabletStatusBarView mStatusBarView; View mNotificationArea; View mNotificationTrigger; NotificationIconArea mNotificationIconArea; ViewGroup mNavigationArea; boolean mNotificationDNDMode; NotificationData.Entry mNotificationDNDDummyEntry; ImageView mBackButton; View mHomeButton; View mMenuButton; View mRecentButton; private boolean mAltBackButtonEnabledForIme; ViewGroup mFeedbackIconArea; // notification icons, IME icon, compat icon InputMethodButton mInputMethodSwitchButton; CompatModeButton mCompatModeButton; NotificationPanel mNotificationPanel; WindowManager.LayoutParams mNotificationPanelParams; NotificationPeekPanel mNotificationPeekWindow; ViewGroup mNotificationPeekRow; int mNotificationPeekIndex; IBinder mNotificationPeekKey; LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight; int mNotificationPeekTapDuration; int mNotificationFlingVelocity; BatteryController mBatteryController; BluetoothController mBluetoothController; LocationController mLocationController; NetworkController mNetworkController; DoNotDisturb mDoNotDisturb; ViewGroup mBarContents; // hide system chrome ("lights out") support View mShadow; NotificationIconArea.IconLayout mIconLayout; TabletTicker mTicker; View mFakeSpaceBar; KeyEvent mSpaceBarKeyEvent = null; View mCompatibilityHelpDialog = null; // for disabling the status bar int mDisabled = 0; private InputMethodsPanel mInputMethodsPanel; private CompatModePanel mCompatModePanel; private int mSystemUiVisibility = 0; private int mNavigationIconHints = 0; private int mShowSearchHoldoff = 0; public Context getContext() { return mContext; } private Runnable mShowSearchPanel = new Runnable() { public void run() { showSearchPanel(); } }; private View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { switch(event.getAction()) { case MotionEvent.ACTION_DOWN: if (!shouldDisableNavbarGestures() && !inKeyguardRestrictedInputMode()) { mHandler.removeCallbacks(mShowSearchPanel); mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mHandler.removeCallbacks(mShowSearchPanel); break; } return false; } }; @Override protected void createAndAddWindows() { addStatusBarWindow(); addPanelWindows(); } private void addStatusBarWindow() { final View sb = makeStatusBarView(); final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.OPAQUE); // We explicitly leave FLAG_HARDWARE_ACCELERATED out of the flags. The status bar occupies // very little screen real-estate and is updated fairly frequently. By using CPU rendering // for the status bar, we prevent the GPU from having to wake up just to do these small // updates, which should help keep power consumption down. lp.gravity = getStatusBarGravity(); lp.setTitle("SystemBar"); lp.packageName = mContext.getPackageName(); mWindowManager.addView(sb, lp); } protected void addPanelWindows() { final Context context = mContext; final Resources res = mContext.getResources(); // Notification Panel mNotificationPanel = (NotificationPanel)View.inflate(context, R.layout.system_bar_notification_panel, null); mNotificationPanel.setBar(this); mNotificationPanel.show(false, false); mNotificationPanel.setOnTouchListener( new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel)); // the battery icon mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery)); mBatteryController.addLabelView( (TextView)mNotificationPanel.findViewById(R.id.battery_text)); // Bt mBluetoothController.addIconView( (ImageView)mNotificationPanel.findViewById(R.id.bluetooth)); // network icons: either a combo icon that switches between mobile and data, or distinct // mobile and data icons final ImageView mobileRSSI = (ImageView)mNotificationPanel.findViewById(R.id.mobile_signal); if (mobileRSSI != null) { mNetworkController.addPhoneSignalIconView(mobileRSSI); } final ImageView wifiRSSI = (ImageView)mNotificationPanel.findViewById(R.id.wifi_signal); if (wifiRSSI != null) { mNetworkController.addWifiIconView(wifiRSSI); } mNetworkController.addWifiLabelView( (TextView)mNotificationPanel.findViewById(R.id.wifi_text)); mNetworkController.addDataTypeIconView( (ImageView)mNotificationPanel.findViewById(R.id.mobile_type)); mNetworkController.addMobileLabelView( (TextView)mNotificationPanel.findViewById(R.id.mobile_text)); mNetworkController.addCombinedLabelView( (TextView)mBarContents.findViewById(R.id.network_text)); mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel); WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams( res.getDimensionPixelSize(R.dimen.notification_panel_width), getNotificationPanelHeight(), WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; lp.setTitle("NotificationPanel"); lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation // lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade mWindowManager.addView(mNotificationPanel, lp); // Search Panel mStatusBarView.setBar(this); mHomeButton.setOnTouchListener(mHomeSearchActionListener); updateSearchPanel(); // Input methods Panel mInputMethodsPanel = (InputMethodsPanel) View.inflate(context, R.layout.system_bar_input_methods_panel, null); mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this); mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener( MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel)); mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton); mStatusBarView.setIgnoreChildren(2, mInputMethodSwitchButton, mInputMethodsPanel); lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; lp.setTitle("InputMethodsPanel"); lp.windowAnimations = R.style.Animation_RecentPanel; mWindowManager.addView(mInputMethodsPanel, lp); // Compatibility mode selector panel mCompatModePanel = (CompatModePanel) View.inflate(context, R.layout.system_bar_compat_mode_panel, null); mCompatModePanel.setOnTouchListener(new TouchOutsideListener( MSG_CLOSE_COMPAT_MODE_PANEL, mCompatModePanel)); mCompatModePanel.setTrigger(mCompatModeButton); mCompatModePanel.setVisibility(View.GONE); mStatusBarView.setIgnoreChildren(3, mCompatModeButton, mCompatModePanel); lp = new WindowManager.LayoutParams( 250, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; lp.setTitle("CompatModePanel"); lp.windowAnimations = android.R.style.Animation_Dialog; mWindowManager.addView(mCompatModePanel, lp); mRecentButton.setOnTouchListener(mRecentsPreloadOnTouchListener); mPile = (NotificationRowLayout)mNotificationPanel.findViewById(R.id.content); mPile.removeAllViews(); mPile.setLongPressListener(getNotificationLongClicker()); ScrollView scroller = (ScrollView)mPile.getParent(); scroller.setFillViewport(true); } @Override protected int getExpandedViewMaxHeight() { return getNotificationPanelHeight(); } private int getNotificationPanelHeight() { final Resources res = mContext.getResources(); final Display d = mWindowManager.getDefaultDisplay(); final Point size = new Point(); d.getRealSize(size); return Math.max(res.getDimensionPixelSize(R.dimen.notification_panel_min_height), size.y); } @Override public void start() { super.start(); // will add the main bar view } @Override protected void onConfigurationChanged(Configuration newConfig) { loadDimens(); mNotificationPanelParams.height = getNotificationPanelHeight(); mWindowManager.updateViewLayout(mNotificationPanel, mNotificationPanelParams); mShowSearchHoldoff = mContext.getResources().getInteger( R.integer.config_show_search_delay); updateSearchPanel(); } protected void loadDimens() { final Resources res = mContext.getResources(); mNaturalBarHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_height); int newIconSize = res.getDimensionPixelSize( com.android.internal.R.dimen.system_bar_icon_size); int newIconHPadding = res.getDimensionPixelSize( R.dimen.status_bar_icon_padding); int newNavIconWidth = res.getDimensionPixelSize(R.dimen.navigation_key_width); int newMenuNavIconWidth = res.getDimensionPixelSize(R.dimen.navigation_menu_key_width); if (mNavigationArea != null && newNavIconWidth != mNavIconWidth) { mNavIconWidth = newNavIconWidth; LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( mNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT); mBackButton.setLayoutParams(lp); mHomeButton.setLayoutParams(lp); mRecentButton.setLayoutParams(lp); } if (mNavigationArea != null && newMenuNavIconWidth != mMenuNavIconWidth) { mMenuNavIconWidth = newMenuNavIconWidth; LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( mMenuNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT); mMenuButton.setLayoutParams(lp); } if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { // Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); mIconHPadding = newIconHPadding; mIconSize = newIconSize; reloadAllNotificationIcons(); // reload the tray } final int numIcons = res.getInteger(R.integer.config_maxNotificationIcons); if (numIcons != mMaxNotificationIcons) { mMaxNotificationIcons = numIcons; if (DEBUG) Slog.d(TAG, "max notification icons: " + mMaxNotificationIcons); reloadAllNotificationIcons(); } } @Override public View getStatusBarView() { return mStatusBarView; } protected View makeStatusBarView() { final Context context = mContext; loadDimens(); final TabletStatusBarView sb = (TabletStatusBarView)View.inflate( context, R.layout.system_bar, null); mStatusBarView = sb; sb.setHandler(mHandler); try { // Sanity-check that someone hasn't set up the config wrong and asked for a navigation // bar on a tablet that has only the system bar if (mWindowManagerService.hasNavigationBar()) { Slog.e(TAG, "Tablet device cannot show navigation bar and system bar"); } } catch (RemoteException ex) { } mBarContents = (ViewGroup) sb.findViewById(R.id.bar_contents); // the whole right-hand side of the bar mNotificationArea = sb.findViewById(R.id.notificationArea); mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener()); // the button to open the notification area mNotificationTrigger = sb.findViewById(R.id.notificationTrigger); // the more notifications icon mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons); // where the icons go mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons); mNotificationPeekTapDuration = ViewConfiguration.getTapTimeout(); mNotificationFlingVelocity = 300; // px/s mTicker = new TabletTicker(this); // The icons mLocationController = new LocationController(mContext); // will post a notification // watch the PREF_DO_NOT_DISTURB and convert to appropriate disable() calls mDoNotDisturb = new DoNotDisturb(mContext); mBatteryController = new BatteryController(mContext); mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); mBluetoothController = new BluetoothController(mContext); mBluetoothController.addIconView((ImageView)sb.findViewById(R.id.bluetooth)); mNetworkController = new NetworkController(mContext); final SignalClusterView signalCluster = (SignalClusterView)sb.findViewById(R.id.signal_cluster); mNetworkController.addSignalCluster(signalCluster); // The navigation buttons mBackButton = (ImageView)sb.findViewById(R.id.back); mNavigationArea = (ViewGroup) sb.findViewById(R.id.navigationArea); mHomeButton = mNavigationArea.findViewById(R.id.home); mMenuButton = mNavigationArea.findViewById(R.id.menu); mRecentButton = mNavigationArea.findViewById(R.id.recent_apps); mRecentButton.setOnClickListener(mOnClickListener); LayoutTransition lt = new LayoutTransition(); lt.setDuration(250); // don't wait for these transitions; we just want icons to fade in/out, not move around lt.setDuration(LayoutTransition.CHANGE_APPEARING, 0); lt.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 0); lt.addTransitionListener(new LayoutTransition.TransitionListener() { public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { // ensure the menu button doesn't stick around on the status bar after it's been // removed mBarContents.invalidate(); } public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {} }); mNavigationArea.setLayoutTransition(lt); // no multi-touch on the nav buttons mNavigationArea.setMotionEventSplittingEnabled(false); // The bar contents buttons mFeedbackIconArea = (ViewGroup)sb.findViewById(R.id.feedbackIconArea); mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton); // Overwrite the lister mInputMethodSwitchButton.setOnClickListener(mOnClickListener); mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton); mCompatModeButton.setOnClickListener(mOnClickListener); mCompatModeButton.setVisibility(View.GONE); // for redirecting errant bar taps to the IME mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar); // "shadows" of the status bar features, for lights-out mode mShadow = sb.findViewById(R.id.bar_shadow); mShadow.setOnTouchListener( new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { // even though setting the systemUI visibility below will turn these views // on, we need them to come up faster so that they can catch this motion // event mShadow.setVisibility(View.GONE); mBarContents.setVisibility(View.VISIBLE); try { mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); } catch (RemoteException ex) { // system process dead } } return false; } }); // tuning parameters final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 750; final int LIGHTS_GOING_OUT_SHADOW_DURATION = 750; final int LIGHTS_GOING_OUT_SHADOW_DELAY = 0; final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200; // final int LIGHTS_COMING_UP_SYSBAR_DELAY = 50; final int LIGHTS_COMING_UP_SHADOW_DURATION = 0; LayoutTransition xition = new LayoutTransition(); xition.setAnimator(LayoutTransition.APPEARING, ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)); xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION); xition.setStartDelay(LayoutTransition.APPEARING, 0); xition.setAnimator(LayoutTransition.DISAPPEARING, ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION); xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition); xition = new LayoutTransition(); xition.setAnimator(LayoutTransition.APPEARING, ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION); xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY); xition.setAnimator(LayoutTransition.DISAPPEARING, ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION); xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition); // set the initial view visibility setAreThereNotifications(); // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); filter.addAction(Intent.ACTION_SCREEN_OFF); context.registerReceiver(mBroadcastReceiver, filter); return sb; } @Override protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( (int) mContext.getResources().getDimension(R.dimen.status_bar_recents_width), ViewGroup.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 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.LEFT; lp.setTitle("RecentsPanel"); 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 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; } else { lp.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; lp.dimAmount = 0.7f; } lp.gravity = Gravity.BOTTOM | Gravity.LEFT; 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(); mSearchPanelView.setStatusBarView(mStatusBarView); mStatusBarView.setDelegateView(mSearchPanelView); } @Override public void showSearchPanel() { super.showSearchPanel(); WindowManager.LayoutParams lp = (android.view.WindowManager.LayoutParams) mStatusBarView.getLayoutParams(); lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWindowManager.updateViewLayout(mStatusBarView, lp); } @Override public void hideSearchPanel() { super.hideSearchPanel(); WindowManager.LayoutParams lp = (android.view.WindowManager.LayoutParams) mStatusBarView.getLayoutParams(); lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWindowManager.updateViewLayout(mStatusBarView, lp); } public int getStatusBarHeight() { return mStatusBarView != null ? mStatusBarView.getHeight() : mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_height); } protected int getStatusBarGravity() { return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL; } public void onBarHeightChanged(int height) { final WindowManager.LayoutParams lp = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams(); if (lp == null) { // haven't been added yet return; } if (lp.height != height) { lp.height = height; mWindowManager.updateViewLayout(mStatusBarView, lp); } } @Override protected BaseStatusBar.H createHandler() { return new TabletStatusBar.H(); } private class H extends BaseStatusBar.H { public void handleMessage(Message m) { super.handleMessage(m); switch (m.what) { case MSG_OPEN_NOTIFICATION_PEEK: if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1); if (m.arg1 >= 0) { final int N = mNotificationData.size(); if (!mNotificationDNDMode) { if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { NotificationData.Entry entry = mNotificationData.get(N-1-mNotificationPeekIndex); entry.icon.setBackgroundColor(0); mNotificationPeekIndex = -1; mNotificationPeekKey = null; } } final int peekIndex = m.arg1; if (peekIndex < N) { //Slog.d(TAG, "loading peek: " + peekIndex); NotificationData.Entry entry = mNotificationDNDMode ? mNotificationDNDDummyEntry : mNotificationData.get(N-1-peekIndex); NotificationData.Entry copy = new NotificationData.Entry( entry.key, entry.notification, entry.icon); inflateViews(copy, mNotificationPeekRow); if (mNotificationDNDMode) { copy.content.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { SharedPreferences.Editor editor = Prefs.edit(mContext); editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false); editor.apply(); animateCollapsePanels(); visibilityChanged(false); } }); } entry.icon.setBackgroundColor(0x20FFFFFF); // mNotificationPeekRow.setLayoutTransition( // peekIndex < mNotificationPeekIndex // ? mNotificationPeekScrubLeft // : mNotificationPeekScrubRight); mNotificationPeekRow.removeAllViews(); mNotificationPeekRow.addView(copy.row); mNotificationPeekWindow.setVisibility(View.VISIBLE); mNotificationPanel.show(false, true); mNotificationPeekIndex = peekIndex; mNotificationPeekKey = entry.key; } } break; case MSG_CLOSE_NOTIFICATION_PEEK: if (DEBUG) Slog.d(TAG, "closing notification peek window"); mNotificationPeekWindow.setVisibility(View.GONE); mNotificationPeekRow.removeAllViews(); final int N = mNotificationData.size(); if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { NotificationData.Entry entry = mNotificationDNDMode ? mNotificationDNDDummyEntry : mNotificationData.get(N-1-mNotificationPeekIndex); entry.icon.setBackgroundColor(0); } mNotificationPeekIndex = -1; mNotificationPeekKey = null; break; case MSG_OPEN_NOTIFICATION_PANEL: if (DEBUG) Slog.d(TAG, "opening notifications panel"); if (!mNotificationPanel.isShowing()) { mNotificationPanel.show(true, true); mNotificationArea.setVisibility(View.INVISIBLE); mTicker.halt(); } break; case MSG_CLOSE_NOTIFICATION_PANEL: if (DEBUG) Slog.d(TAG, "closing notifications panel"); if (mNotificationPanel.isShowing()) { mNotificationPanel.show(false, true); mNotificationArea.setVisibility(View.VISIBLE); } break; case MSG_OPEN_INPUT_METHODS_PANEL: if (DEBUG) Slog.d(TAG, "opening input methods panel"); if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel(); break; case MSG_CLOSE_INPUT_METHODS_PANEL: if (DEBUG) Slog.d(TAG, "closing input methods panel"); if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false); break; case MSG_OPEN_COMPAT_MODE_PANEL: if (DEBUG) Slog.d(TAG, "opening compat panel"); if (mCompatModePanel != null) mCompatModePanel.openPanel(); break; case MSG_CLOSE_COMPAT_MODE_PANEL: if (DEBUG) Slog.d(TAG, "closing compat panel"); if (mCompatModePanel != null) mCompatModePanel.closePanel(); break; case MSG_SHOW_CHROME: if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)"); mBarContents.setVisibility(View.VISIBLE); mShadow.setVisibility(View.GONE); mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; notifyUiVisibilityChanged(); break; case MSG_HIDE_CHROME: if (DEBUG) Slog.d(TAG, "showing shadows (lights out)"); animateCollapsePanels(); visibilityChanged(false); mBarContents.setVisibility(View.GONE); mShadow.setVisibility(View.VISIBLE); mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE; notifyUiVisibilityChanged(); break; case MSG_STOP_TICKER: mTicker.halt(); break; } } } public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon); } public void updateIcon(String slot, int index, int viewIndex, StatusBarIcon old, StatusBarIcon icon) { if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon); } public void removeIcon(String slot, int index, int viewIndex) { if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")"); } public void addNotification(IBinder key, StatusBarNotification notification) { if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); addNotificationViews(key, notification); final boolean immersive = isImmersive(); if (false && immersive) { // TODO: immersive mode popups for tablet } else if (notification.notification.fullScreenIntent != null) { // not immersive & a full-screen alert should be shown Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;" + " sending fullScreenIntent"); try { notification.notification.fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } } else { tick(key, notification, true); } setAreThereNotifications(); } public void removeNotification(IBinder key) { if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ")"); removeNotificationViews(key); mTicker.remove(key); setAreThereNotifications(); } public void showClock(boolean show) { View clock = mBarContents.findViewById(R.id.clock); View network_text = mBarContents.findViewById(R.id.network_text); if (clock != null) { clock.setVisibility(show ? View.VISIBLE : View.GONE); } if (network_text != null) { network_text.setVisibility((!show) ? View.VISIBLE : View.GONE); } } public void disable(int state) { int old = mDisabled; int diff = state ^ old; mDisabled = state; // act accordingly if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes")); showClock(show); } if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0; Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes")); mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE); } if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { Slog.i(TAG, "DISABLE_EXPAND: yes"); animateCollapsePanels(); visibilityChanged(false); } } if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { mNotificationDNDMode = Prefs.read(mContext) .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT); if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":"")); mTicker.halt(); } else { Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":"")); } // refresh icons to show either notifications or the DND message reloadAllNotificationIcons(); } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { mTicker.halt(); } } if ((diff & (StatusBarManager.DISABLE_RECENT | StatusBarManager.DISABLE_BACK | StatusBarManager.DISABLE_HOME)) != 0) { setNavigationVisibility(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); } } } private void setNavigationVisibility(int visibility) { boolean disableHome = ((visibility & StatusBarManager.DISABLE_HOME) != 0); boolean disableRecent = ((visibility & StatusBarManager.DISABLE_RECENT) != 0); boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0); mBackButton.setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); mHomeButton.setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); mRecentButton.setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); mInputMethodSwitchButton.setScreenLocked( (visibility & StatusBarManager.DISABLE_SYSTEM_INFO) != 0); } private boolean hasTicker(Notification n) { return n.tickerView != null || !TextUtils.isEmpty(n.tickerText); } @Override protected void tick(IBinder key, StatusBarNotification n, boolean firstTime) { // Don't show the ticker when the windowshade is open. if (mNotificationPanel.isShowing()) { return; } // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification // if it's a new notification. if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) { 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 (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) { if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { mTicker.add(key, n); mFeedbackIconArea.setVisibility(View.GONE); } } } // called by TabletTicker when it's done with all queued ticks public void doneTicking() { mFeedbackIconArea.setVisibility(View.VISIBLE); } public void animateExpandNotificationsPanel() { mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); } public void animateCollapsePanels() { animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); } public void animateCollapsePanels(int flags) { if ((flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); } 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); } if ((flags & CommandQueue.FLAG_EXCLUDE_INPUT_METHODS_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL); } if ((flags & CommandQueue.FLAG_EXCLUDE_COMPAT_MODE_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL); } } @Override public void animateExpandSettingsPanel() { // TODO: Implement when TabletStatusBar begins to be used. } @Override // CommandQueue public void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; if (DEBUG) { android.widget.Toast.makeText(mContext, "Navigation icon hints = " + hints, 500).show(); } mNavigationIconHints = hints; mBackButton.setAlpha( (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f); mHomeButton.setAlpha( (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f); mRecentButton.setAlpha( (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f); mBackButton.setImageResource( (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT)) ? R.drawable.ic_sysbar_back_ime : R.drawable.ic_sysbar_back); } private void notifyUiVisibilityChanged() { try { mWindowManagerService.statusBarVisibilityChanged(mSystemUiVisibility); } catch (RemoteException ex) { } } @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 (diff != 0) { mSystemUiVisibility = newVal; if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) { mHandler.removeMessages(MSG_HIDE_CHROME); mHandler.removeMessages(MSG_SHOW_CHROME); mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) ? MSG_SHOW_CHROME : MSG_HIDE_CHROME); } notifyUiVisibilityChanged(); } } public void setLightsOn(boolean on) { // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app // that can't handle lights-out mode. if (mMenuButton.getVisibility() == View.VISIBLE) { on = true; } Slog.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); } } public void topAppWindowChanged(boolean showMenu) { if (DEBUG) { Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); } mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE); // See above re: lights-out policy for legacy apps. if (showMenu) setLightsOn(true); mCompatModeButton.refresh(); if (mCompatModeButton.getVisibility() == View.VISIBLE) { if (DEBUG_COMPAT_HELP || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) { showCompatibilityHelp(); } } else { hideCompatibilityHelp(); mCompatModePanel.closePanel(); } } private void showCompatibilityHelp() { if (mCompatibilityHelpDialog != null) { return; } mCompatibilityHelpDialog = View.inflate(mContext, R.layout.compat_mode_help, null); View button = mCompatibilityHelpDialog.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { hideCompatibilityHelp(); SharedPreferences.Editor editor = Prefs.edit(mContext); editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true); editor.apply(); } }); WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, PixelFormat.TRANSLUCENT); lp.setTitle("CompatibilityModeDialog"); lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade mWindowManager.addView(mCompatibilityHelpDialog, lp); } private void hideCompatibilityHelp() { if (mCompatibilityHelpDialog != null) { mWindowManager.removeView(mCompatibilityHelpDialog); mCompatibilityHelpDialog = null; } } public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { mInputMethodSwitchButton.setImeWindowStatus(token, (vis & InputMethodService.IME_ACTIVE) != 0); updateNotificationIcons(); mInputMethodsPanel.setImeToken(token); boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || ((vis & InputMethodService.IME_VISIBLE) != 0); mAltBackButtonEnabledForIme = altBack; mCommandQueue.setNavigationIconHints( altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT) : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT)); if (FAKE_SPACE_BAR) { mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0) ? View.VISIBLE : View.GONE); } } @Override public void setHardKeyboardStatus(boolean available, boolean enabled) { if (DEBUG) { Slog.d(TAG, "Set hard keyboard status: available=" + available + ", enabled=" + enabled); } mInputMethodSwitchButton.setHardKeyboardStatus(available); updateNotificationIcons(); mInputMethodsPanel.setHardKeyboardStatus(available, enabled); } @Override public void onHardKeyboardEnabledChange(boolean enabled) { try { mBarService.setHardKeyboardEnabled(enabled); } catch (RemoteException ex) { } } private boolean isImmersive() { try { return ActivityManagerNative.getDefault().isTopActivityImmersive(); //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); } catch (RemoteException ex) { // the end is nigh return false; } } @Override protected void setAreThereNotifications() { if (mNotificationPanel != null) { mNotificationPanel.setClearable(isDeviceProvisioned() && mNotificationData.hasClearableItems()); } } private View.OnClickListener mOnClickListener = new View.OnClickListener() { public void onClick(View v) { if (v == mRecentButton) { onClickRecentButton(); } else if (v == mInputMethodSwitchButton) { onClickInputMethodSwitchButton(); } else if (v == mCompatModeButton) { onClickCompatModeButton(); } } }; public void onClickRecentButton() { if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { toggleRecentApps(); } } public void onClickInputMethodSwitchButton() { if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled); int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ? MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL; mHandler.removeMessages(msg); mHandler.sendEmptyMessage(msg); } public void onClickCompatModeButton() { int msg = (mCompatModePanel.getVisibility() == View.GONE) ? MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL; mHandler.removeMessages(msg); mHandler.sendEmptyMessage(msg); } private class NotificationTriggerTouchListener implements View.OnTouchListener { VelocityTracker mVT; float mInitialTouchX, mInitialTouchY; int mTouchSlop; public NotificationTriggerTouchListener() { mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } private Runnable mHiliteOnR = new Runnable() { public void run() { mNotificationArea.setBackgroundResource( com.android.internal.R.drawable.list_selector_pressed_holo_dark); }}; public void hilite(final boolean on) { if (on) { mNotificationArea.postDelayed(mHiliteOnR, 100); } else { mNotificationArea.removeCallbacks(mHiliteOnR); mNotificationArea.setBackground(null); } } public boolean onTouch(View v, MotionEvent event) { // Slog.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)", // event.getX(), // event.getY(), // mInitialTouchX, // mInitialTouchY)); if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { return true; } final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mVT = VelocityTracker.obtain(); mInitialTouchX = event.getX(); mInitialTouchY = event.getY(); hilite(true); // fall through case MotionEvent.ACTION_OUTSIDE: case MotionEvent.ACTION_MOVE: // check for fling if (mVT != null) { mVT.addMovement(event); mVT.computeCurrentVelocity(1000); // pixels per second // require a little more oomph once we're already in peekaboo mode if (mVT.getYVelocity() < -mNotificationFlingVelocity) { animateExpandNotificationsPanel(); visibilityChanged(true); hilite(false); mVT.recycle(); mVT = null; } } return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: hilite(false); if (mVT != null) { if (action == MotionEvent.ACTION_UP // was this a sloppy tap? && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) // dragging off the bottom doesn't count && (int)event.getY() < v.getBottom()) { animateExpandNotificationsPanel(); visibilityChanged(true); v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); v.playSoundEffect(SoundEffectConstants.CLICK); } mVT.recycle(); mVT = null; return true; } } return false; } } public void resetNotificationPeekFadeTimer() { if (DEBUG) { Slog.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY + "ms from now"); } mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, NOTIFICATION_PEEK_FADE_DELAY); } private void reloadAllNotificationIcons() { if (mIconLayout == null) return; mIconLayout.removeAllViews(); updateNotificationIcons(); } @Override protected void updateNotificationIcons() { // XXX: need to implement a new limited linear layout class // to avoid removing & readding everything if (mIconLayout == null) return; // first, populate the main notification panel loadNotificationPanel(); final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); // alternate behavior in DND mode if (mNotificationDNDMode) { if (mIconLayout.getChildCount() == 0) { final Notification dndNotification = new Notification.Builder(mContext) .setContentTitle(mContext.getText(R.string.notifications_off_title)) .setContentText(mContext.getText(R.string.notifications_off_text)) .setSmallIcon(R.drawable.ic_notification_dnd) .setOngoing(true) .getNotification(); final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd", dndNotification); iconView.setImageResource(R.drawable.ic_notification_dnd); iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0); mNotificationDNDDummyEntry = new NotificationData.Entry( null, new StatusBarNotification("", 0, "", 0, 0, Notification.PRIORITY_MAX, dndNotification, android.os.Process.myUserHandle()), iconView); mIconLayout.addView(iconView, params); } return; } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) { // if icons are disabled but we're not in DND mode, this is probably Setup and we should // just leave the area totally empty return; } int N = mNotificationData.size(); if (DEBUG) { Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); } ArrayList toShow = new ArrayList(); // Extra Special Icons // The IME switcher and compatibility mode icons take the place of notifications. You didn't // need to see all those new emails, did you? int maxNotificationIconsCount = mMaxNotificationIcons; if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount --; if (mCompatModeButton.getVisibility() != View.GONE) maxNotificationIconsCount --; final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; toShow.size()< maxNotificationIconsCount; i++) { if (i >= N) break; Entry ent = mNotificationData.get(N-i-1); if ((provisioned && ent.notification.score >= HIDE_ICONS_BELOW_SCORE) || showNotificationEvenIfUnprovisioned(ent.notification)) { toShow.add(ent.icon); } } ArrayList toRemove = new ArrayList(); for (int i=0; i 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