/* * 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.os.ServiceManager; import android.text.TextUtils; import android.util.Slog; import android.view.Display; import android.view.Gravity; import android.view.IWindowManager; 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.WindowManagerImpl; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RemoteViews; 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.recent.RecentTasksLoader; import com.android.systemui.recent.RecentsPanelView; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.DoNotDisturb; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.NotificationData.Entry; 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, RecentsPanelView.OnRecentsPanelVisibilityChangedListener { 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 boolean NOTIFICATION_PEEK_ENABLED = false; 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; IWindowManager mWindowManager; // tracking all current notifications private NotificationData mNotificationData = new NotificationData(); 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; NotificationRowLayout mPile; 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; public Context getContext() { return mContext; } @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, // We use a pixel format of RGB565 for the status bar to save memory bandwidth and // to ensure that the layer can be handled by HWComposer. On some devices the // HWComposer is unable to handle SW-rendered RGBX_8888 layers. PixelFormat.RGB_565); // 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(); WindowManagerImpl.getDefault().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 WindowManagerImpl.getDefault().addView(mNotificationPanel, lp); // Notification preview window if (NOTIFICATION_PEEK_ENABLED) { mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context, R.layout.system_bar_notification_peek, null); mNotificationPeekWindow.setBar(this); mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content); mNotificationPeekWindow.setVisibility(View.GONE); mNotificationPeekWindow.setOnTouchListener( new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow)); mNotificationPeekScrubRight = new LayoutTransition(); mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, ObjectAnimator.ofInt(null, "left", -512, 0)); mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, ObjectAnimator.ofInt(null, "left", -512, 0)); mNotificationPeekScrubRight.setDuration(500); mNotificationPeekScrubLeft = new LayoutTransition(); mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, ObjectAnimator.ofInt(null, "left", 512, 0)); mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, ObjectAnimator.ofInt(null, "left", 512, 0)); mNotificationPeekScrubLeft.setDuration(500); // XXX: setIgnoreChildren? lp = new WindowManager.LayoutParams( 512, // ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; lp.y = res.getDimensionPixelOffset(R.dimen.peek_window_y_offset); lp.setTitle("NotificationPeekWindow"); lp.windowAnimations = com.android.internal.R.style.Animation_Toast; WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp); } // Recents Panel mRecentTasksLoader = new RecentTasksLoader(context); updateRecentsPanel(); // Search Panel mStatusBarView.setBar(this); 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; WindowManagerImpl.getDefault().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; WindowManagerImpl.getDefault().addView(mCompatModePanel, lp); mRecentButton.setOnTouchListener(mRecentsPanel); mPile = (NotificationRowLayout)mNotificationPanel.findViewById(R.id.content); mPile.removeAllViews(); mPile.setLongPressListener(getNotificationLongClicker()); ScrollView scroller = (ScrollView)mPile.getParent(); scroller.setFillViewport(true); } private int getNotificationPanelHeight() { final Resources res = mContext.getResources(); final Display d = WindowManagerImpl.getDefault().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(); WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel, mNotificationPanelParams); mRecentsPanel.updateValuesFromResources(); } 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(); } } public View getStatusBarView() { return mStatusBarView; } protected View makeStatusBarView() { final Context context = mContext; mWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); 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 (mWindowManager.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); if (!NOTIFICATION_PEEK_ENABLED) { mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener()); } // the button to open the notification area mNotificationTrigger = sb.findViewById(R.id.notificationTrigger); if (NOTIFICATION_PEEK_ENABLED) { mNotificationTrigger.setOnTouchListener(new NotificationTriggerTouchListener()); } // the more notifications icon mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons); // where the icons go mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons); if (NOTIFICATION_PEEK_ENABLED) { mIconLayout.setOnTouchListener(new NotificationIconTouchListener()); } ViewConfiguration vc = ViewConfiguration.get(context); mNotificationPeekTapDuration = vc.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(mDisplay)) { 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; } protected void updateRecentsPanel() { super.updateRecentsPanel(R.layout.system_bar_recent_panel); mRecentsPanel.setStatusBarView(mStatusBarView); } @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; lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; WindowManagerImpl.getDefault().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; lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; WindowManagerImpl.getDefault().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; final WindowManager wm = WindowManagerImpl.getDefault(); wm.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(); animateCollapse(); 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()) { if (NOTIFICATION_PEEK_ENABLED) { mNotificationPeekWindow.setVisibility(View.GONE); } 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)"); animateCollapse(); 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 updateNotification(IBinder key, StatusBarNotification notification) { if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); if (oldEntry == null) { Slog.w(TAG, "updateNotification for unknown key: " + key); return; } final StatusBarNotification oldNotification = oldEntry.notification; // XXX: modify when we do something more intelligent with the two content views final RemoteViews oldContentView = (oldNotification.notification.bigContentView != null) ? oldNotification.notification.bigContentView : oldNotification.notification.contentView; final RemoteViews contentView = (notification.notification.bigContentView != null) ? notification.notification.bigContentView : notification.notification.contentView; if (DEBUG) { Slog.d(TAG, "old notification: when=" + oldNotification.notification.when + " ongoing=" + oldNotification.isOngoing() + " expanded=" + oldEntry.expanded + " contentView=" + oldContentView + " rowParent=" + oldEntry.row.getParent()); Slog.d(TAG, "new notification: when=" + notification.notification.when + " ongoing=" + oldNotification.isOngoing() + " contentView=" + contentView); } // Can we just reapply the RemoteViews in place? If when didn't change, the order // didn't change. boolean contentsUnchanged = oldEntry.expanded != null && contentView != null && oldContentView != null && contentView.getPackage() != null && oldContentView.getPackage() != null && oldContentView.getPackage().equals(contentView.getPackage()) && oldContentView.getLayoutId() == contentView.getLayoutId(); ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); boolean orderUnchanged = notification.notification.when==oldNotification.notification.when && notification.score == oldNotification.score; // score now encompasses/supersedes isOngoing() boolean updateTicker = notification.notification.tickerText != null && !TextUtils.equals(notification.notification.tickerText, oldEntry.notification.notification.tickerText); boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1; if (contentsUnchanged && (orderUnchanged || isLastAnyway)) { if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); oldEntry.notification = notification; try { // Reapply the RemoteViews contentView.reapply(mContext, oldEntry.content); // update the contentIntent final PendingIntent contentIntent = notification.notification.contentIntent; if (contentIntent != null) { final View.OnClickListener listener = makeClicker(contentIntent, notification.pkg, notification.tag, notification.id); oldEntry.content.setOnClickListener(listener); } else { oldEntry.content.setOnClickListener(null); } // Update the icon. final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon, notification.notification.iconLevel, notification.notification.number, notification.notification.tickerText); if (!oldEntry.icon.set(ic)) { handleNotificationError(key, notification, "Couldn't update icon: " + ic); return; } if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { // must update the peek window Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); peekMsg.arg1 = mNotificationPeekIndex; mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); mHandler.sendMessage(peekMsg); } } catch (RuntimeException e) { // It failed to add cleanly. Log, and remove the view from the panel. Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); removeNotificationViews(key); addNotificationViews(key, notification); } } else { if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); removeNotificationViews(key); addNotificationViews(key, notification); } // Restart the ticker if it's still running if (updateTicker) { mTicker.halt(); tick(key, notification, false); } 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"); animateCollapse(); 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); } private 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 animateExpand() { if (NOTIFICATION_PEEK_ENABLED) { mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); } mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); } public void animateCollapse() { animateCollapse(false); } private void animateCollapse(boolean excludeRecents) { mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); if (!excludeRecents) { mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); } mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL); mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL); if (NOTIFICATION_PEEK_ENABLED) { mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); } } @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 { mWindowManager.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 WindowManagerImpl.getDefault().addView(mCompatibilityHelpDialog, lp); } private void hideCompatibilityHelp() { if (mCompatibilityHelpDialog != null) { WindowManagerImpl.getDefault().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 onRecentsPanelVisibilityChanged(boolean visible) { boolean altBack = visible || mAltBackButtonEnabledForIme; mCommandQueue.setNavigationIconHints( altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT) : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT)); } @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; } } private void setAreThereNotifications() { if (mNotificationPanel != null) { mNotificationPanel.setClearable(mNotificationData.hasClearableItems()); } } /** * Cancel this notification and tell the status bar service about the failure. Hold no locks. */ void handleNotificationError(IBinder key, StatusBarNotification n, String message) { removeNotification(key); try { mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); } catch (RemoteException ex) { // The end is nigh. } } 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) { int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; mHandler.removeMessages(msg); mHandler.sendEmptyMessage(msg); } } 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); } StatusBarNotification removeNotificationViews(IBinder key) { NotificationData.Entry entry = mNotificationData.remove(key); if (entry == null) { Slog.w(TAG, "removeNotification for unknown key: " + key); return null; } // Remove the expanded view. ViewGroup rowParent = (ViewGroup)entry.row.getParent(); if (rowParent != null) rowParent.removeView(entry.row); if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { // must close the peek as well, since it's gone mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); } // Remove the icon. // ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); // if (iconParent != null) iconParent.removeView(entry.icon); updateNotificationIcons(); return entry.notification; } 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.setBackgroundDrawable(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) { animateExpand(); 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()) { animateExpand(); 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 class NotificationIconTouchListener implements View.OnTouchListener { VelocityTracker mVT; int mPeekIndex; float mInitialTouchX, mInitialTouchY; int mTouchSlop; public NotificationIconTouchListener() { mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } public boolean onTouch(View v, MotionEvent event) { boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; boolean panelShowing = mNotificationPanel.isShowing(); if (panelShowing) return false; int numIcons = mIconLayout.getChildCount(); int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth()); if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1; else if (newPeekIndex < 0) newPeekIndex = 0; final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mVT = VelocityTracker.obtain(); mInitialTouchX = event.getX(); mInitialTouchY = event.getY(); mPeekIndex = -1; // fall through case MotionEvent.ACTION_OUTSIDE: case MotionEvent.ACTION_MOVE: // peek and switch icons if necessary if (newPeekIndex != mPeekIndex) { mPeekIndex = newPeekIndex; if (DEBUG) Slog.d(TAG, "will peek at notification #" + mPeekIndex); Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); peekMsg.arg1 = mPeekIndex; mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); if (peeking) { // no delay if we're scrubbing left-right mHandler.sendMessage(peekMsg); } else { // wait for fling mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH); } } // 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 (!panelShowing && ( (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); } } return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); if (!peeking) { 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()) { Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); peekMsg.arg1 = mPeekIndex; mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); mHandler.sendMessage(peekMsg); v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); v.playSoundEffect(SoundEffectConstants.CLICK); peeking = true; // not technically true yet, but the next line will run } } if (peeking) { resetNotificationPeekFadeTimer(); } mVT.recycle(); mVT = null; return true; } return false; } } StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { if (DEBUG) { Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); } // Construct the icon. final StatusBarIconView iconView = new StatusBarIconView(mContext, notification.pkg + "/0x" + Integer.toHexString(notification.id), notification.notification); iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon, notification.notification.iconLevel, notification.notification.number, notification.notification.tickerText); if (!iconView.set(ic)) { handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); return null; } // Construct the expanded view. NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); if (!inflateViews(entry, mPile)) { handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " + notification); return null; } // Add the icon. int pos = mNotificationData.add(entry); if (DEBUG) { Slog.d(TAG, "addNotificationViews: added at " + pos); } updateNotificationIcons(); return iconView; } private void reloadAllNotificationIcons() { if (mIconLayout == null) return; mIconLayout.removeAllViews(); updateNotificationIcons(); } private 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), 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 --; for (int i=0; toShow.size()< maxNotificationIconsCount; i++) { if (i >= N) break; Entry ent = mNotificationData.get(N-i-1); if (ent.notification.score >= HIDE_ICONS_BELOW_SCORE) { toShow.add(ent.icon); } } ArrayList toRemove = new ArrayList(); for (int i=0; i toShow = new ArrayList(); for (int i=0; i toRemove = new ArrayList(); for (int i=0; i