| /* |
| * 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 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; |
| |
| 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; |
| |
| public Context getContext() { return mContext; } |
| |
| private View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() { |
| public boolean onTouch(View v, MotionEvent event) { |
| switch(event.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| if (!shouldDisableNavbarGestures()) { |
| showSearchPanel(); |
| } |
| 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, |
| // 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); |
| |
| // Recents Panel |
| mRecentTasksLoader = new RecentTasksLoader(context); |
| updateRecentsPanel(); |
| |
| // 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; |
| |
| 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); |
| } |
| |
| @Override |
| protected int getExpandedViewMaxHeight() { |
| return getNotificationPanelHeight(); |
| } |
| |
| 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); |
| 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); |
| |
| 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; |
| 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; |
| 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()) { |
| 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 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); |
| } |
| |
| @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 animateExpand() { |
| 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); |
| mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL); |
| mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL); |
| } |
| |
| @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; |
| } |
| } |
| |
| @Override |
| protected void setAreThereNotifications() { |
| if (mNotificationPanel != null) { |
| mNotificationPanel.setClearable(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) { |
| 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); |
| } |
| |
| 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; |
| } |
| } |
| |
| 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), |
| 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<View> toShow = new ArrayList<View>(); |
| |
| // 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<View> toRemove = new ArrayList<View>(); |
| for (int i=0; i<mIconLayout.getChildCount(); i++) { |
| View child = mIconLayout.getChildAt(i); |
| if (!toShow.contains(child)) { |
| toRemove.add(child); |
| } |
| } |
| |
| for (View remove : toRemove) { |
| mIconLayout.removeView(remove); |
| } |
| |
| for (int i=0; i<toShow.size(); i++) { |
| View v = toShow.get(i); |
| v.setPadding(mIconHPadding, 0, mIconHPadding, 0); |
| if (v.getParent() == null) { |
| mIconLayout.addView(v, i, params); |
| } |
| } |
| } |
| |
| private void loadNotificationPanel() { |
| int N = mNotificationData.size(); |
| |
| ArrayList<View> toShow = new ArrayList<View>(); |
| |
| for (int i=0; i<N; i++) { |
| View row = mNotificationData.get(N-i-1).row; |
| toShow.add(row); |
| } |
| |
| ArrayList<View> toRemove = new ArrayList<View>(); |
| for (int i=0; i<mPile.getChildCount(); i++) { |
| View child = mPile.getChildAt(i); |
| if (!toShow.contains(child)) { |
| toRemove.add(child); |
| } |
| } |
| |
| for (View remove : toRemove) { |
| mPile.removeView(remove); |
| } |
| |
| for (int i=0; i<toShow.size(); i++) { |
| View v = toShow.get(i); |
| if (v.getParent() == null) { |
| // the notification panel has the most important things at the bottom |
| mPile.addView(v, N-1-i); |
| } |
| } |
| |
| mNotificationPanel.setNotificationCount(N); |
| } |
| |
| @Override |
| protected void workAroundBadLayerDrawableOpacity(View v) { |
| Drawable bgd = v.getBackground(); |
| if (!(bgd instanceof LayerDrawable)) return; |
| |
| LayerDrawable d = (LayerDrawable) bgd; |
| v.setBackgroundDrawable(null); |
| d.setOpacity(PixelFormat.TRANSLUCENT); |
| v.setBackgroundDrawable(d); |
| } |
| |
| public void clearAll() { |
| try { |
| mBarService.onClearAllNotifications(); |
| } catch (RemoteException ex) { |
| // system process is dead if we're here. |
| } |
| animateCollapse(); |
| visibilityChanged(false); |
| } |
| |
| private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) |
| || Intent.ACTION_SCREEN_OFF.equals(action)) { |
| boolean excludeRecents = false; |
| if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { |
| String reason = intent.getStringExtra("reason"); |
| if (reason != null) { |
| excludeRecents = reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS); |
| } |
| } |
| if (Intent.ACTION_SCREEN_OFF.equals(action)) { |
| // If we're turning the screen off, we want to hide the |
| // recents panel with no animation |
| // TODO: hide other things, like the notification tray, |
| // with no animation as well |
| mRecentsPanel.show(false, false); |
| excludeRecents = true; |
| } |
| animateCollapse(excludeRecents); |
| } |
| } |
| }; |
| |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.print("mDisabled=0x"); |
| pw.println(Integer.toHexString(mDisabled)); |
| pw.println("mNetworkController:"); |
| mNetworkController.dump(fd, pw, args); |
| } |
| |
| @Override |
| protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { |
| return parent.indexOfChild(entry.row) == parent.getChildCount()-1; |
| } |
| |
| @Override |
| protected void haltTicker() { |
| mTicker.halt(); |
| } |
| |
| @Override |
| protected void updateExpandedViewPos(int expandedPosition) { |
| } |
| |
| @Override |
| protected boolean shouldDisableNavbarGestures() { |
| return mNotificationPanel.getVisibility() == View.VISIBLE |
| || (mDisabled & StatusBarManager.DISABLE_HOME) != 0; |
| } |
| } |
| |
| |