Home | History | Annotate | Download | only in tablet
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.systemui.statusbar.tablet;
     18 
     19 import java.io.FileDescriptor;
     20 import java.io.PrintWriter;
     21 import java.util.ArrayList;
     22 
     23 import android.animation.LayoutTransition;
     24 import android.animation.ObjectAnimator;
     25 import android.app.ActivityManagerNative;
     26 import android.app.KeyguardManager;
     27 import android.app.Notification;
     28 import android.app.PendingIntent;
     29 import android.app.StatusBarManager;
     30 import android.content.BroadcastReceiver;
     31 import android.content.Context;
     32 import android.content.Intent;
     33 import android.content.IntentFilter;
     34 import android.content.SharedPreferences;
     35 import android.content.pm.ApplicationInfo;
     36 import android.content.pm.PackageManager.NameNotFoundException;
     37 import android.content.res.Configuration;
     38 import android.content.res.Resources;
     39 import android.inputmethodservice.InputMethodService;
     40 import android.graphics.PixelFormat;
     41 import android.graphics.Point;
     42 import android.graphics.Rect;
     43 import android.graphics.drawable.Drawable;
     44 import android.graphics.drawable.LayerDrawable;
     45 import android.os.Build;
     46 import android.os.Handler;
     47 import android.os.IBinder;
     48 import android.os.Message;
     49 import android.os.RemoteException;
     50 import android.os.ServiceManager;
     51 import android.text.TextUtils;
     52 import android.util.Slog;
     53 import android.view.accessibility.AccessibilityEvent;
     54 import android.view.Display;
     55 import android.view.Gravity;
     56 import android.view.IWindowManager;
     57 import android.view.KeyEvent;
     58 import android.view.LayoutInflater;
     59 import android.view.MotionEvent;
     60 import android.view.SoundEffectConstants;
     61 import android.view.VelocityTracker;
     62 import android.view.View;
     63 import android.view.ViewConfiguration;
     64 import android.view.ViewGroup;
     65 import android.view.WindowManager;
     66 import android.view.WindowManagerImpl;
     67 import android.widget.ImageView;
     68 import android.widget.LinearLayout;
     69 import android.widget.RemoteViews;
     70 import android.widget.ScrollView;
     71 import android.widget.TextView;
     72 
     73 import com.android.internal.statusbar.StatusBarIcon;
     74 import com.android.internal.statusbar.StatusBarNotification;
     75 import com.android.systemui.R;
     76 import com.android.systemui.recent.RecentTasksLoader;
     77 import com.android.systemui.recent.RecentsPanelView;
     78 import com.android.systemui.statusbar.NotificationData;
     79 import com.android.systemui.statusbar.SignalClusterView;
     80 import com.android.systemui.statusbar.StatusBar;
     81 import com.android.systemui.statusbar.StatusBarIconView;
     82 import com.android.systemui.statusbar.policy.BatteryController;
     83 import com.android.systemui.statusbar.policy.BluetoothController;
     84 import com.android.systemui.statusbar.policy.CompatModeButton;
     85 import com.android.systemui.statusbar.policy.LocationController;
     86 import com.android.systemui.statusbar.policy.NetworkController;
     87 import com.android.systemui.statusbar.policy.Prefs;
     88 
     89 public class TabletStatusBar extends StatusBar implements
     90         HeightReceiver.OnBarHeightChangedListener,
     91         InputMethodsPanel.OnHardKeyboardEnabledChangeListener {
     92     public static final boolean DEBUG = false;
     93     public static final boolean DEBUG_COMPAT_HELP = false;
     94     public static final String TAG = "TabletStatusBar";
     95 
     96 
     97     public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
     98     public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
     99     public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002;
    100     public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003;
    101     public static final int MSG_OPEN_RECENTS_PANEL = 1020;
    102     public static final int MSG_CLOSE_RECENTS_PANEL = 1021;
    103     public static final int MSG_SHOW_CHROME = 1030;
    104     public static final int MSG_HIDE_CHROME = 1031;
    105     public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040;
    106     public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041;
    107     public static final int MSG_OPEN_COMPAT_MODE_PANEL = 1050;
    108     public static final int MSG_CLOSE_COMPAT_MODE_PANEL = 1051;
    109     public static final int MSG_STOP_TICKER = 2000;
    110 
    111     // Fitts' Law assistance for LatinIME; see policy.EventHole
    112     private static final boolean FAKE_SPACE_BAR = true;
    113 
    114     // Notification "peeking" (flyover preview of individual notifications)
    115     final static boolean NOTIFICATION_PEEK_ENABLED = false;
    116     final static int NOTIFICATION_PEEK_HOLD_THRESH = 200; // ms
    117     final static int NOTIFICATION_PEEK_FADE_DELAY = 3000; // ms
    118 
    119     // The height of the bar, as definied by the build.  It may be taller if we're plugged
    120     // into hdmi.
    121     int mNaturalBarHeight = -1;
    122     int mIconSize = -1;
    123     int mIconHPadding = -1;
    124     private int mMaxNotificationIcons = 5;
    125 
    126     H mHandler = new H();
    127 
    128     IWindowManager mWindowManager;
    129 
    130     // tracking all current notifications
    131     private NotificationData mNotificationData = new NotificationData();
    132 
    133     TabletStatusBarView mStatusBarView;
    134     View mNotificationArea;
    135     View mNotificationTrigger;
    136     NotificationIconArea mNotificationIconArea;
    137     ViewGroup mNavigationArea;
    138 
    139     boolean mNotificationDNDMode;
    140     NotificationData.Entry mNotificationDNDDummyEntry;
    141 
    142     ImageView mBackButton;
    143     View mHomeButton;
    144     View mMenuButton;
    145     View mRecentButton;
    146 
    147     ViewGroup mFeedbackIconArea; // notification icons, IME icon, compat icon
    148     InputMethodButton mInputMethodSwitchButton;
    149     CompatModeButton mCompatModeButton;
    150 
    151     NotificationPanel mNotificationPanel;
    152     WindowManager.LayoutParams mNotificationPanelParams;
    153     NotificationPeekPanel mNotificationPeekWindow;
    154     ViewGroup mNotificationPeekRow;
    155     int mNotificationPeekIndex;
    156     IBinder mNotificationPeekKey;
    157     LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight;
    158 
    159     int mNotificationPeekTapDuration;
    160     int mNotificationFlingVelocity;
    161 
    162     ViewGroup mPile;
    163 
    164     HeightReceiver mHeightReceiver;
    165     BatteryController mBatteryController;
    166     BluetoothController mBluetoothController;
    167     LocationController mLocationController;
    168     NetworkController mNetworkController;
    169 
    170     ViewGroup mBarContents;
    171 
    172     // hide system chrome ("lights out") support
    173     View mShadow;
    174 
    175     NotificationIconArea.IconLayout mIconLayout;
    176 
    177     TabletTicker mTicker;
    178 
    179     View mFakeSpaceBar;
    180     KeyEvent mSpaceBarKeyEvent = null;
    181 
    182     View mCompatibilityHelpDialog = null;
    183 
    184     // for disabling the status bar
    185     int mDisabled = 0;
    186 
    187     private RecentsPanelView mRecentsPanel;
    188     private RecentTasksLoader mRecentTasksLoader;
    189     private InputMethodsPanel mInputMethodsPanel;
    190     private CompatModePanel mCompatModePanel;
    191 
    192     private int mSystemUiVisibility = 0;
    193     // used to notify status bar for suppressing notification LED
    194     private boolean mPanelSlightlyVisible;
    195 
    196     public Context getContext() { return mContext; }
    197 
    198     protected void addPanelWindows() {
    199         final Context context = mContext;
    200         final Resources res = mContext.getResources();
    201 
    202         // Notification Panel
    203         mNotificationPanel = (NotificationPanel)View.inflate(context,
    204                 R.layout.status_bar_notification_panel, null);
    205         mNotificationPanel.setBar(this);
    206         mNotificationPanel.show(false, false);
    207         mNotificationPanel.setOnTouchListener(
    208                 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel));
    209 
    210         // the battery icon
    211         mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery));
    212         mBatteryController.addLabelView(
    213                 (TextView)mNotificationPanel.findViewById(R.id.battery_text));
    214 
    215         // Bt
    216         mBluetoothController.addIconView(
    217                 (ImageView)mNotificationPanel.findViewById(R.id.bluetooth));
    218 
    219         // network icons: either a combo icon that switches between mobile and data, or distinct
    220         // mobile and data icons
    221         final ImageView mobileRSSI =
    222                 (ImageView)mNotificationPanel.findViewById(R.id.mobile_signal);
    223         if (mobileRSSI != null) {
    224             mNetworkController.addPhoneSignalIconView(mobileRSSI);
    225         }
    226         final ImageView wifiRSSI =
    227                 (ImageView)mNotificationPanel.findViewById(R.id.wifi_signal);
    228         if (wifiRSSI != null) {
    229             mNetworkController.addWifiIconView(wifiRSSI);
    230         }
    231         mNetworkController.addWifiLabelView(
    232                 (TextView)mNotificationPanel.findViewById(R.id.wifi_text));
    233 
    234         mNetworkController.addDataTypeIconView(
    235                 (ImageView)mNotificationPanel.findViewById(R.id.mobile_type));
    236         mNetworkController.addMobileLabelView(
    237                 (TextView)mNotificationPanel.findViewById(R.id.mobile_text));
    238         mNetworkController.addCombinedLabelView(
    239                 (TextView)mBarContents.findViewById(R.id.network_text));
    240 
    241         mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
    242 
    243         WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams(
    244                 res.getDimensionPixelSize(R.dimen.notification_panel_width),
    245                 getNotificationPanelHeight(),
    246                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
    247                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    248                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    249                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
    250                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
    251                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    252                 PixelFormat.TRANSLUCENT);
    253         lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
    254         lp.setTitle("NotificationPanel");
    255         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
    256                 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
    257         lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation
    258 //        lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
    259 
    260         WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
    261 
    262         // Notification preview window
    263         if (NOTIFICATION_PEEK_ENABLED) {
    264             mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context,
    265                     R.layout.status_bar_notification_peek, null);
    266             mNotificationPeekWindow.setBar(this);
    267 
    268             mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content);
    269             mNotificationPeekWindow.setVisibility(View.GONE);
    270             mNotificationPeekWindow.setOnTouchListener(
    271                     new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow));
    272             mNotificationPeekScrubRight = new LayoutTransition();
    273             mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING,
    274                     ObjectAnimator.ofInt(null, "left", -512, 0));
    275             mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING,
    276                     ObjectAnimator.ofInt(null, "left", -512, 0));
    277             mNotificationPeekScrubRight.setDuration(500);
    278 
    279             mNotificationPeekScrubLeft = new LayoutTransition();
    280             mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING,
    281                     ObjectAnimator.ofInt(null, "left", 512, 0));
    282             mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING,
    283                     ObjectAnimator.ofInt(null, "left", 512, 0));
    284             mNotificationPeekScrubLeft.setDuration(500);
    285 
    286             // XXX: setIgnoreChildren?
    287             lp = new WindowManager.LayoutParams(
    288                     512, // ViewGroup.LayoutParams.WRAP_CONTENT,
    289                     ViewGroup.LayoutParams.WRAP_CONTENT,
    290                     WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
    291                     WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    292                         | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
    293                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
    294                     PixelFormat.TRANSLUCENT);
    295             lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
    296             lp.y = res.getDimensionPixelOffset(R.dimen.peek_window_y_offset);
    297             lp.setTitle("NotificationPeekWindow");
    298             lp.windowAnimations = com.android.internal.R.style.Animation_Toast;
    299 
    300             WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp);
    301         }
    302 
    303         // Recents Panel
    304         mRecentTasksLoader = new RecentTasksLoader(context);
    305         mRecentsPanel = (RecentsPanelView) View.inflate(context,
    306                 R.layout.status_bar_recent_panel, null);
    307         mRecentsPanel.setVisibility(View.GONE);
    308         mRecentsPanel.setSystemUiVisibility(View.STATUS_BAR_DISABLE_BACK);
    309         mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
    310                 mRecentsPanel));
    311         mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader);
    312         mRecentTasksLoader.setRecentsPanel(mRecentsPanel);
    313         mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel);
    314 
    315         lp = new WindowManager.LayoutParams(
    316                 (int) res.getDimension(R.dimen.status_bar_recents_width),
    317                 ViewGroup.LayoutParams.MATCH_PARENT,
    318                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
    319                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    320                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
    321                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
    322                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    323                 PixelFormat.TRANSLUCENT);
    324         lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
    325         lp.setTitle("RecentsPanel");
    326         lp.windowAnimations = R.style.Animation_RecentPanel;
    327         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
    328                 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
    329 
    330         WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
    331         mRecentsPanel.setBar(this);
    332 
    333         // Input methods Panel
    334         mInputMethodsPanel = (InputMethodsPanel) View.inflate(context,
    335                 R.layout.status_bar_input_methods_panel, null);
    336         mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this);
    337         mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener(
    338                 MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel));
    339         mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton);
    340         mStatusBarView.setIgnoreChildren(3, mInputMethodSwitchButton, mInputMethodsPanel);
    341         lp = new WindowManager.LayoutParams(
    342                 ViewGroup.LayoutParams.WRAP_CONTENT,
    343                 ViewGroup.LayoutParams.WRAP_CONTENT,
    344                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
    345                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    346                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
    347                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
    348                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    349                 PixelFormat.TRANSLUCENT);
    350         lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
    351         lp.setTitle("InputMethodsPanel");
    352         lp.windowAnimations = R.style.Animation_RecentPanel;
    353 
    354         WindowManagerImpl.getDefault().addView(mInputMethodsPanel, lp);
    355 
    356         // Compatibility mode selector panel
    357         mCompatModePanel = (CompatModePanel) View.inflate(context,
    358                 R.layout.status_bar_compat_mode_panel, null);
    359         mCompatModePanel.setOnTouchListener(new TouchOutsideListener(
    360                 MSG_CLOSE_COMPAT_MODE_PANEL, mCompatModePanel));
    361         mCompatModePanel.setTrigger(mCompatModeButton);
    362         mCompatModePanel.setVisibility(View.GONE);
    363         mStatusBarView.setIgnoreChildren(4, mCompatModeButton, mCompatModePanel);
    364         lp = new WindowManager.LayoutParams(
    365                 250,
    366                 ViewGroup.LayoutParams.WRAP_CONTENT,
    367                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
    368                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    369                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
    370                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
    371                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    372                 PixelFormat.TRANSLUCENT);
    373         lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
    374         lp.setTitle("CompatModePanel");
    375         lp.windowAnimations = android.R.style.Animation_Dialog;
    376 
    377         WindowManagerImpl.getDefault().addView(mCompatModePanel, lp);
    378     }
    379 
    380     private int getNotificationPanelHeight() {
    381         final Resources res = mContext.getResources();
    382         final Display d = WindowManagerImpl.getDefault().getDefaultDisplay();
    383         final Point size = new Point();
    384         d.getRealSize(size);
    385         return Math.max(res.getDimensionPixelSize(R.dimen.notification_panel_min_height), size.y);
    386     }
    387 
    388     @Override
    389     public void start() {
    390         super.start(); // will add the main bar view
    391     }
    392 
    393     @Override
    394     protected void onConfigurationChanged(Configuration newConfig) {
    395         mHeightReceiver.updateHeight(); // display size may have changed
    396         loadDimens();
    397         mNotificationPanelParams.height = getNotificationPanelHeight();
    398         WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel,
    399                 mNotificationPanelParams);
    400         mRecentsPanel.updateValuesFromResources();
    401     }
    402 
    403     protected void loadDimens() {
    404         final Resources res = mContext.getResources();
    405 
    406         mNaturalBarHeight = res.getDimensionPixelSize(
    407                 com.android.internal.R.dimen.system_bar_height);
    408 
    409         int newIconSize = res.getDimensionPixelSize(
    410             com.android.internal.R.dimen.system_bar_icon_size);
    411         int newIconHPadding = res.getDimensionPixelSize(
    412             R.dimen.status_bar_icon_padding);
    413 
    414         if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) {
    415 //            Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding);
    416             mIconHPadding = newIconHPadding;
    417             mIconSize = newIconSize;
    418             reloadAllNotificationIcons(); // reload the tray
    419         }
    420 
    421         final int numIcons = res.getInteger(R.integer.config_maxNotificationIcons);
    422         if (numIcons != mMaxNotificationIcons) {
    423             mMaxNotificationIcons = numIcons;
    424             if (DEBUG) Slog.d(TAG, "max notification icons: " + mMaxNotificationIcons);
    425             reloadAllNotificationIcons();
    426         }
    427     }
    428 
    429     protected View makeStatusBarView() {
    430         final Context context = mContext;
    431 
    432         mWindowManager = IWindowManager.Stub.asInterface(
    433                 ServiceManager.getService(Context.WINDOW_SERVICE));
    434 
    435         // This guy will listen for HDMI plugged broadcasts so we can resize the
    436         // status bar as appropriate.
    437         mHeightReceiver = new HeightReceiver(mContext);
    438         mHeightReceiver.registerReceiver();
    439         loadDimens();
    440 
    441         final TabletStatusBarView sb = (TabletStatusBarView)View.inflate(
    442                 context, R.layout.status_bar, null);
    443         mStatusBarView = sb;
    444 
    445         sb.setHandler(mHandler);
    446 
    447         try {
    448             // Sanity-check that someone hasn't set up the config wrong and asked for a navigation
    449             // bar on a tablet that has only the system bar
    450             if (mWindowManager.hasNavigationBar()) {
    451                 throw new RuntimeException(
    452                         "Tablet device cannot show navigation bar and system bar");
    453             }
    454         } catch (RemoteException ex) {
    455         }
    456 
    457         mBarContents = (ViewGroup) sb.findViewById(R.id.bar_contents);
    458 
    459         // the whole right-hand side of the bar
    460         mNotificationArea = sb.findViewById(R.id.notificationArea);
    461         if (!NOTIFICATION_PEEK_ENABLED) {
    462             mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener());
    463         }
    464 
    465         // the button to open the notification area
    466         mNotificationTrigger = sb.findViewById(R.id.notificationTrigger);
    467         if (NOTIFICATION_PEEK_ENABLED) {
    468             mNotificationTrigger.setOnTouchListener(new NotificationTriggerTouchListener());
    469         }
    470 
    471         // the more notifications icon
    472         mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
    473 
    474         // where the icons go
    475         mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
    476         if (NOTIFICATION_PEEK_ENABLED) {
    477             mIconLayout.setOnTouchListener(new NotificationIconTouchListener());
    478         }
    479 
    480         ViewConfiguration vc = ViewConfiguration.get(context);
    481         mNotificationPeekTapDuration = vc.getTapTimeout();
    482         mNotificationFlingVelocity = 300; // px/s
    483 
    484         mTicker = new TabletTicker(this);
    485 
    486         // The icons
    487         mLocationController = new LocationController(mContext); // will post a notification
    488 
    489         mBatteryController = new BatteryController(mContext);
    490         mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
    491         mBluetoothController = new BluetoothController(mContext);
    492         mBluetoothController.addIconView((ImageView)sb.findViewById(R.id.bluetooth));
    493 
    494         mNetworkController = new NetworkController(mContext);
    495         final SignalClusterView signalCluster =
    496                 (SignalClusterView)sb.findViewById(R.id.signal_cluster);
    497         mNetworkController.addSignalCluster(signalCluster);
    498 
    499         // The navigation buttons
    500         mBackButton = (ImageView)sb.findViewById(R.id.back);
    501         mNavigationArea = (ViewGroup) sb.findViewById(R.id.navigationArea);
    502         mHomeButton = mNavigationArea.findViewById(R.id.home);
    503         mMenuButton = mNavigationArea.findViewById(R.id.menu);
    504         mRecentButton = mNavigationArea.findViewById(R.id.recent_apps);
    505         mRecentButton.setOnClickListener(mOnClickListener);
    506 
    507         LayoutTransition lt = new LayoutTransition();
    508         lt.setDuration(250);
    509         // don't wait for these transitions; we just want icons to fade in/out, not move around
    510         lt.setDuration(LayoutTransition.CHANGE_APPEARING, 0);
    511         lt.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 0);
    512         lt.addTransitionListener(new LayoutTransition.TransitionListener() {
    513             public void endTransition(LayoutTransition transition, ViewGroup container,
    514                     View view, int transitionType) {
    515                 // ensure the menu button doesn't stick around on the status bar after it's been
    516                 // removed
    517                 mBarContents.invalidate();
    518             }
    519             public void startTransition(LayoutTransition transition, ViewGroup container,
    520                     View view, int transitionType) {}
    521         });
    522         mNavigationArea.setLayoutTransition(lt);
    523         // no multi-touch on the nav buttons
    524         mNavigationArea.setMotionEventSplittingEnabled(false);
    525 
    526         // The bar contents buttons
    527         mFeedbackIconArea = (ViewGroup)sb.findViewById(R.id.feedbackIconArea);
    528         mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton);
    529         // Overwrite the lister
    530         mInputMethodSwitchButton.setOnClickListener(mOnClickListener);
    531 
    532         mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton);
    533         mCompatModeButton.setOnClickListener(mOnClickListener);
    534         mCompatModeButton.setVisibility(View.GONE);
    535 
    536         // for redirecting errant bar taps to the IME
    537         mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar);
    538 
    539         // "shadows" of the status bar features, for lights-out mode
    540         mShadow = sb.findViewById(R.id.bar_shadow);
    541         mShadow.setOnTouchListener(
    542             new View.OnTouchListener() {
    543                 public boolean onTouch(View v, MotionEvent ev) {
    544                     if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    545                         // even though setting the systemUI visibility below will turn these views
    546                         // on, we need them to come up faster so that they can catch this motion
    547                         // event
    548                         mShadow.setVisibility(View.GONE);
    549                         mBarContents.setVisibility(View.VISIBLE);
    550 
    551                         try {
    552                             mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
    553                         } catch (RemoteException ex) {
    554                             // system process dead
    555                         }
    556                     }
    557                     return false;
    558                 }
    559             });
    560 
    561         // tuning parameters
    562         final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 600;
    563         final int LIGHTS_GOING_OUT_SHADOW_DURATION = 1000;
    564         final int LIGHTS_GOING_OUT_SHADOW_DELAY    = 500;
    565 
    566         final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200;
    567 //        final int LIGHTS_COMING_UP_SYSBAR_DELAY    = 50;
    568         final int LIGHTS_COMING_UP_SHADOW_DURATION = 0;
    569 
    570         LayoutTransition xition = new LayoutTransition();
    571         xition.setAnimator(LayoutTransition.APPEARING,
    572                ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f));
    573         xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION);
    574         xition.setStartDelay(LayoutTransition.APPEARING, 0);
    575         xition.setAnimator(LayoutTransition.DISAPPEARING,
    576                ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
    577         xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION);
    578         xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
    579         ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition);
    580 
    581         xition = new LayoutTransition();
    582         xition.setAnimator(LayoutTransition.APPEARING,
    583                ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
    584         xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION);
    585         xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY);
    586         xition.setAnimator(LayoutTransition.DISAPPEARING,
    587                ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
    588         xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION);
    589         xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
    590         ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition);
    591 
    592         // set the initial view visibility
    593         setAreThereNotifications();
    594 
    595         // Add the windows
    596         addPanelWindows();
    597         mRecentButton.setOnTouchListener(mRecentsPanel);
    598 
    599         mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
    600         mPile.removeAllViews();
    601 
    602         ScrollView scroller = (ScrollView)mPile.getParent();
    603         scroller.setFillViewport(true);
    604 
    605         mHeightReceiver.addOnBarHeightChangedListener(this);
    606 
    607         // receive broadcasts
    608         IntentFilter filter = new IntentFilter();
    609         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    610         filter.addAction(Intent.ACTION_SCREEN_OFF);
    611         context.registerReceiver(mBroadcastReceiver, filter);
    612 
    613         return sb;
    614     }
    615 
    616     public int getStatusBarHeight() {
    617         return mHeightReceiver.getHeight();
    618     }
    619 
    620     protected int getStatusBarGravity() {
    621         return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
    622     }
    623 
    624     public void onBarHeightChanged(int height) {
    625         final WindowManager.LayoutParams lp
    626                 = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams();
    627         if (lp == null) {
    628             // haven't been added yet
    629             return;
    630         }
    631         if (lp.height != height) {
    632             lp.height = height;
    633             final WindowManager wm = WindowManagerImpl.getDefault();
    634             wm.updateViewLayout(mStatusBarView, lp);
    635         }
    636     }
    637 
    638     private class H extends Handler {
    639         public void handleMessage(Message m) {
    640             switch (m.what) {
    641                 case MSG_OPEN_NOTIFICATION_PEEK:
    642                     if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1);
    643 
    644                     if (m.arg1 >= 0) {
    645                         final int N = mNotificationData.size();
    646 
    647                         if (!mNotificationDNDMode) {
    648                             if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
    649                                 NotificationData.Entry entry = mNotificationData.get(N-1-mNotificationPeekIndex);
    650                                 entry.icon.setBackgroundColor(0);
    651                                 mNotificationPeekIndex = -1;
    652                                 mNotificationPeekKey = null;
    653                             }
    654                         }
    655 
    656                         final int peekIndex = m.arg1;
    657                         if (peekIndex < N) {
    658                             //Slog.d(TAG, "loading peek: " + peekIndex);
    659                             NotificationData.Entry entry =
    660                                 mNotificationDNDMode
    661                                     ? mNotificationDNDDummyEntry
    662                                     : mNotificationData.get(N-1-peekIndex);
    663                             NotificationData.Entry copy = new NotificationData.Entry(
    664                                     entry.key,
    665                                     entry.notification,
    666                                     entry.icon);
    667                             inflateViews(copy, mNotificationPeekRow);
    668 
    669                             if (mNotificationDNDMode) {
    670                                 copy.content.setOnClickListener(new View.OnClickListener() {
    671                                     public void onClick(View v) {
    672                                         SharedPreferences.Editor editor = Prefs.edit(mContext);
    673                                         editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false);
    674                                         editor.apply();
    675                                         animateCollapse();
    676                                         visibilityChanged(false);
    677                                     }
    678                                 });
    679                             }
    680 
    681                             entry.icon.setBackgroundColor(0x20FFFFFF);
    682 
    683 //                          mNotificationPeekRow.setLayoutTransition(
    684 //                              peekIndex < mNotificationPeekIndex
    685 //                                  ? mNotificationPeekScrubLeft
    686 //                                  : mNotificationPeekScrubRight);
    687 
    688                             mNotificationPeekRow.removeAllViews();
    689                             mNotificationPeekRow.addView(copy.row);
    690 
    691                             mNotificationPeekWindow.setVisibility(View.VISIBLE);
    692                             mNotificationPanel.show(false, true);
    693 
    694                             mNotificationPeekIndex = peekIndex;
    695                             mNotificationPeekKey = entry.key;
    696                         }
    697                     }
    698                     break;
    699                 case MSG_CLOSE_NOTIFICATION_PEEK:
    700                     if (DEBUG) Slog.d(TAG, "closing notification peek window");
    701                     mNotificationPeekWindow.setVisibility(View.GONE);
    702                     mNotificationPeekRow.removeAllViews();
    703 
    704                     final int N = mNotificationData.size();
    705                     if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
    706                         NotificationData.Entry entry =
    707                             mNotificationDNDMode
    708                                 ? mNotificationDNDDummyEntry
    709                                 : mNotificationData.get(N-1-mNotificationPeekIndex);
    710                         entry.icon.setBackgroundColor(0);
    711                     }
    712 
    713                     mNotificationPeekIndex = -1;
    714                     mNotificationPeekKey = null;
    715                     break;
    716                 case MSG_OPEN_NOTIFICATION_PANEL:
    717                     if (DEBUG) Slog.d(TAG, "opening notifications panel");
    718                     if (!mNotificationPanel.isShowing()) {
    719                         if (NOTIFICATION_PEEK_ENABLED) {
    720                             mNotificationPeekWindow.setVisibility(View.GONE);
    721                         }
    722                         mNotificationPanel.show(true, true);
    723                         mNotificationArea.setVisibility(View.INVISIBLE);
    724                         mTicker.halt();
    725                     }
    726                     break;
    727                 case MSG_CLOSE_NOTIFICATION_PANEL:
    728                     if (DEBUG) Slog.d(TAG, "closing notifications panel");
    729                     if (mNotificationPanel.isShowing()) {
    730                         mNotificationPanel.show(false, true);
    731                         mNotificationArea.setVisibility(View.VISIBLE);
    732                     }
    733                     break;
    734                 case MSG_OPEN_RECENTS_PANEL:
    735                     if (DEBUG) Slog.d(TAG, "opening recents panel");
    736                     if (mRecentsPanel != null) {
    737                         mRecentsPanel.show(true, true);
    738                     }
    739                     break;
    740                 case MSG_CLOSE_RECENTS_PANEL:
    741                     if (DEBUG) Slog.d(TAG, "closing recents panel");
    742                     if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
    743                         mRecentsPanel.show(false, true);
    744                     }
    745                     break;
    746                 case MSG_OPEN_INPUT_METHODS_PANEL:
    747                     if (DEBUG) Slog.d(TAG, "opening input methods panel");
    748                     if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel();
    749                     break;
    750                 case MSG_CLOSE_INPUT_METHODS_PANEL:
    751                     if (DEBUG) Slog.d(TAG, "closing input methods panel");
    752                     if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false);
    753                     break;
    754                 case MSG_OPEN_COMPAT_MODE_PANEL:
    755                     if (DEBUG) Slog.d(TAG, "opening compat panel");
    756                     if (mCompatModePanel != null) mCompatModePanel.openPanel();
    757                     break;
    758                 case MSG_CLOSE_COMPAT_MODE_PANEL:
    759                     if (DEBUG) Slog.d(TAG, "closing compat panel");
    760                     if (mCompatModePanel != null) mCompatModePanel.closePanel();
    761                     break;
    762                 case MSG_SHOW_CHROME:
    763                     if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)");
    764                     mBarContents.setVisibility(View.VISIBLE);
    765                     mShadow.setVisibility(View.GONE);
    766                     mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
    767                     notifyUiVisibilityChanged();
    768                     break;
    769                 case MSG_HIDE_CHROME:
    770                     if (DEBUG) Slog.d(TAG, "showing shadows (lights out)");
    771                     animateCollapse();
    772                     visibilityChanged(false);
    773                     mBarContents.setVisibility(View.GONE);
    774                     mShadow.setVisibility(View.VISIBLE);
    775                     mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
    776                     notifyUiVisibilityChanged();
    777                     break;
    778                 case MSG_STOP_TICKER:
    779                     mTicker.halt();
    780                     break;
    781             }
    782         }
    783     }
    784 
    785     public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
    786         if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon);
    787     }
    788 
    789     public void updateIcon(String slot, int index, int viewIndex,
    790             StatusBarIcon old, StatusBarIcon icon) {
    791         if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon);
    792     }
    793 
    794     public void removeIcon(String slot, int index, int viewIndex) {
    795         if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")");
    796     }
    797 
    798     public void addNotification(IBinder key, StatusBarNotification notification) {
    799         if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
    800         addNotificationViews(key, notification);
    801 
    802         final boolean immersive = isImmersive();
    803         if (false && immersive) {
    804             // TODO: immersive mode popups for tablet
    805         } else if (notification.notification.fullScreenIntent != null) {
    806             // not immersive & a full-screen alert should be shown
    807             Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;"
    808                     + " sending fullScreenIntent");
    809             try {
    810                 notification.notification.fullScreenIntent.send();
    811             } catch (PendingIntent.CanceledException e) {
    812             }
    813         } else {
    814             tick(key, notification, true);
    815         }
    816 
    817         setAreThereNotifications();
    818     }
    819 
    820     public void updateNotification(IBinder key, StatusBarNotification notification) {
    821         if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
    822 
    823         final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
    824         if (oldEntry == null) {
    825             Slog.w(TAG, "updateNotification for unknown key: " + key);
    826             return;
    827         }
    828 
    829         final StatusBarNotification oldNotification = oldEntry.notification;
    830         final RemoteViews oldContentView = oldNotification.notification.contentView;
    831 
    832         final RemoteViews contentView = notification.notification.contentView;
    833 
    834         if (DEBUG) {
    835             Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
    836                     + " ongoing=" + oldNotification.isOngoing()
    837                     + " expanded=" + oldEntry.expanded
    838                     + " contentView=" + oldContentView
    839                     + " rowParent=" + oldEntry.row.getParent());
    840             Slog.d(TAG, "new notification: when=" + notification.notification.when
    841                     + " ongoing=" + oldNotification.isOngoing()
    842                     + " contentView=" + contentView);
    843         }
    844 
    845         // Can we just reapply the RemoteViews in place?  If when didn't change, the order
    846         // didn't change.
    847         boolean contentsUnchanged = oldEntry.expanded != null
    848                 && contentView != null && oldContentView != null
    849                 && contentView.getPackage() != null
    850                 && oldContentView.getPackage() != null
    851                 && oldContentView.getPackage().equals(contentView.getPackage())
    852                 && oldContentView.getLayoutId() == contentView.getLayoutId();
    853         ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
    854         boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
    855                 && notification.priority == oldNotification.priority;
    856                 // priority now encompasses isOngoing()
    857         boolean updateTicker = notification.notification.tickerText != null
    858                 && !TextUtils.equals(notification.notification.tickerText,
    859                         oldEntry.notification.notification.tickerText);
    860         boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1;
    861         if (contentsUnchanged && (orderUnchanged || isLastAnyway)) {
    862             if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
    863             oldEntry.notification = notification;
    864             try {
    865                 // Reapply the RemoteViews
    866                 contentView.reapply(mContext, oldEntry.content);
    867                 // update the contentIntent
    868                 final PendingIntent contentIntent = notification.notification.contentIntent;
    869                 if (contentIntent != null) {
    870                     final View.OnClickListener listener = new NotificationClicker(contentIntent,
    871                             notification.pkg, notification.tag, notification.id);
    872                     oldEntry.largeIcon.setOnClickListener(listener);
    873                     oldEntry.content.setOnClickListener(listener);
    874                 } else {
    875                     oldEntry.largeIcon.setOnClickListener(null);
    876                     oldEntry.content.setOnClickListener(null);
    877                 }
    878                 // Update the icon.
    879                 final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
    880                         notification.notification.icon, notification.notification.iconLevel,
    881                         notification.notification.number,
    882                         notification.notification.tickerText);
    883                 if (!oldEntry.icon.set(ic)) {
    884                     handleNotificationError(key, notification, "Couldn't update icon: " + ic);
    885                     return;
    886                 }
    887                 // Update the large icon
    888                 if (notification.notification.largeIcon != null) {
    889                     oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon);
    890                 } else {
    891                     oldEntry.largeIcon.getLayoutParams().width = 0;
    892                     oldEntry.largeIcon.setVisibility(View.INVISIBLE);
    893                 }
    894 
    895                 if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) {
    896                     // must update the peek window
    897                     Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
    898                     peekMsg.arg1 = mNotificationPeekIndex;
    899                     mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
    900                     mHandler.sendMessage(peekMsg);
    901                 }
    902             }
    903             catch (RuntimeException e) {
    904                 // It failed to add cleanly.  Log, and remove the view from the panel.
    905                 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
    906                 removeNotificationViews(key);
    907                 addNotificationViews(key, notification);
    908             }
    909         } else {
    910             if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
    911             removeNotificationViews(key);
    912             addNotificationViews(key, notification);
    913         }
    914 
    915         // Restart the ticker if it's still running
    916         if (updateTicker) {
    917             mTicker.halt();
    918             tick(key, notification, false);
    919         }
    920 
    921         setAreThereNotifications();
    922     }
    923 
    924     public void removeNotification(IBinder key) {
    925         if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ")");
    926         removeNotificationViews(key);
    927         mTicker.remove(key);
    928         setAreThereNotifications();
    929     }
    930 
    931     public void showClock(boolean show) {
    932         View clock = mBarContents.findViewById(R.id.clock);
    933         View network_text = mBarContents.findViewById(R.id.network_text);
    934         if (clock != null) {
    935             clock.setVisibility(show ? View.VISIBLE : View.GONE);
    936         }
    937         if (network_text != null) {
    938             network_text.setVisibility((!show) ? View.VISIBLE : View.GONE);
    939         }
    940     }
    941 
    942     public void disable(int state) {
    943         int old = mDisabled;
    944         int diff = state ^ old;
    945         mDisabled = state;
    946 
    947         // act accordingly
    948         if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
    949             boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
    950             Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes"));
    951             showClock(show);
    952         }
    953         if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
    954             boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0;
    955             Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes"));
    956             mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE);
    957         }
    958         if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
    959             if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
    960                 Slog.i(TAG, "DISABLE_EXPAND: yes");
    961                 animateCollapse();
    962                 visibilityChanged(false);
    963             }
    964         }
    965         if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
    966             mNotificationDNDMode = Prefs.read(mContext)
    967                         .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT);
    968 
    969             if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
    970                 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":""));
    971                 mTicker.halt();
    972             } else {
    973                 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":""));
    974             }
    975 
    976             // refresh icons to show either notifications or the DND message
    977             reloadAllNotificationIcons();
    978         } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
    979             if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
    980                 mTicker.halt();
    981             }
    982         }
    983         if ((diff & (StatusBarManager.DISABLE_RECENT
    984                         | StatusBarManager.DISABLE_BACK
    985                         | StatusBarManager.DISABLE_HOME)) != 0) {
    986             setNavigationVisibility(state);
    987 
    988             if ((state & StatusBarManager.DISABLE_RECENT) != 0) {
    989                 // close recents if it's visible
    990                 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
    991                 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
    992             }
    993         }
    994     }
    995 
    996     private void setNavigationVisibility(int visibility) {
    997         boolean disableHome = ((visibility & StatusBarManager.DISABLE_HOME) != 0);
    998         boolean disableRecent = ((visibility & StatusBarManager.DISABLE_RECENT) != 0);
    999         boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0);
   1000 
   1001         mBackButton.setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
   1002         mHomeButton.setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
   1003         mRecentButton.setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
   1004 
   1005         mInputMethodSwitchButton.setScreenLocked(
   1006                 (visibility & StatusBarManager.DISABLE_SYSTEM_INFO) != 0);
   1007     }
   1008 
   1009     private boolean hasTicker(Notification n) {
   1010         return n.tickerView != null || !TextUtils.isEmpty(n.tickerText);
   1011     }
   1012 
   1013     private void tick(IBinder key, StatusBarNotification n, boolean firstTime) {
   1014         // Don't show the ticker when the windowshade is open.
   1015         if (mNotificationPanel.isShowing()) {
   1016             return;
   1017         }
   1018         // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification
   1019         // if it's a new notification.
   1020         if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) {
   1021             return;
   1022         }
   1023         // Show the ticker if one is requested. Also don't do this
   1024         // until status bar window is attached to the window manager,
   1025         // because...  well, what's the point otherwise?  And trying to
   1026         // run a ticker without being attached will crash!
   1027         if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
   1028             if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
   1029                             | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
   1030                 mTicker.add(key, n);
   1031                 mFeedbackIconArea.setVisibility(View.GONE);
   1032             }
   1033         }
   1034     }
   1035 
   1036     // called by TabletTicker when it's done with all queued ticks
   1037     public void doneTicking() {
   1038         mFeedbackIconArea.setVisibility(View.VISIBLE);
   1039     }
   1040 
   1041     public void animateExpand() {
   1042         if (NOTIFICATION_PEEK_ENABLED) {
   1043             mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
   1044             mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
   1045             mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
   1046         }
   1047         mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
   1048         mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
   1049     }
   1050 
   1051     public void animateCollapse() {
   1052         animateCollapse(false);
   1053     }
   1054 
   1055     private void animateCollapse(boolean excludeRecents) {
   1056         mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
   1057         mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
   1058         if (!excludeRecents) {
   1059             mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
   1060             mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
   1061         }
   1062         mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL);
   1063         mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL);
   1064         mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL);
   1065         mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL);
   1066         if (NOTIFICATION_PEEK_ENABLED) {
   1067             mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
   1068             mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
   1069         }
   1070     }
   1071 
   1072     /**
   1073      * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
   1074      * This was added last-minute and is inconsistent with the way the rest of the notifications
   1075      * are handled, because the notification isn't really cancelled.  The lights are just
   1076      * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
   1077      * this is what he wants. (see bug 1131461)
   1078      */
   1079     void visibilityChanged(boolean visible) {
   1080         if (mPanelSlightlyVisible != visible) {
   1081             mPanelSlightlyVisible = visible;
   1082             try {
   1083                 mBarService.onPanelRevealed();
   1084             } catch (RemoteException ex) {
   1085                 // Won't fail unless the world has ended.
   1086             }
   1087         }
   1088     }
   1089 
   1090     private void notifyUiVisibilityChanged() {
   1091         try {
   1092             mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility);
   1093         } catch (RemoteException ex) {
   1094         }
   1095     }
   1096 
   1097     @Override // CommandQueue
   1098     public void setSystemUiVisibility(int vis) {
   1099         if (vis != mSystemUiVisibility) {
   1100             mSystemUiVisibility = vis;
   1101 
   1102             mHandler.removeMessages(MSG_HIDE_CHROME);
   1103             mHandler.removeMessages(MSG_SHOW_CHROME);
   1104             mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE)
   1105                     ? MSG_SHOW_CHROME : MSG_HIDE_CHROME);
   1106 
   1107             notifyUiVisibilityChanged();
   1108         }
   1109     }
   1110 
   1111     public void setLightsOn(boolean on) {
   1112         // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app
   1113         // that can't handle lights-out mode.
   1114         if (mMenuButton.getVisibility() == View.VISIBLE) {
   1115             on = true;
   1116         }
   1117 
   1118         Slog.v(TAG, "setLightsOn(" + on + ")");
   1119         if (on) {
   1120             setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE);
   1121         } else {
   1122             setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE);
   1123         }
   1124     }
   1125 
   1126     public void topAppWindowChanged(boolean showMenu) {
   1127         if (DEBUG) {
   1128             Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button");
   1129         }
   1130         mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE);
   1131 
   1132         // See above re: lights-out policy for legacy apps.
   1133         if (showMenu) setLightsOn(true);
   1134 
   1135         mCompatModeButton.refresh();
   1136         if (mCompatModeButton.getVisibility() == View.VISIBLE) {
   1137             if (DEBUG_COMPAT_HELP
   1138                     || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) {
   1139                 showCompatibilityHelp();
   1140             }
   1141         } else {
   1142             hideCompatibilityHelp();
   1143             mCompatModePanel.closePanel();
   1144         }
   1145     }
   1146 
   1147     private void showCompatibilityHelp() {
   1148         if (mCompatibilityHelpDialog != null) {
   1149             return;
   1150         }
   1151 
   1152         mCompatibilityHelpDialog = View.inflate(mContext, R.layout.compat_mode_help, null);
   1153         View button = mCompatibilityHelpDialog.findViewById(R.id.button);
   1154 
   1155         button.setOnClickListener(new View.OnClickListener() {
   1156             @Override
   1157             public void onClick(View v) {
   1158                 hideCompatibilityHelp();
   1159                 SharedPreferences.Editor editor = Prefs.edit(mContext);
   1160                 editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true);
   1161                 editor.apply();
   1162             }
   1163         });
   1164 
   1165         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
   1166                 ViewGroup.LayoutParams.MATCH_PARENT,
   1167                 ViewGroup.LayoutParams.MATCH_PARENT,
   1168                 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
   1169                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
   1170                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
   1171                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
   1172                 PixelFormat.TRANSLUCENT);
   1173         lp.setTitle("CompatibilityModeDialog");
   1174         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
   1175                 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
   1176         lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
   1177 
   1178         WindowManagerImpl.getDefault().addView(mCompatibilityHelpDialog, lp);
   1179     }
   1180 
   1181     private void hideCompatibilityHelp() {
   1182         if (mCompatibilityHelpDialog != null) {
   1183             WindowManagerImpl.getDefault().removeView(mCompatibilityHelpDialog);
   1184             mCompatibilityHelpDialog = null;
   1185         }
   1186     }
   1187 
   1188     public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
   1189         mInputMethodSwitchButton.setImeWindowStatus(token,
   1190                 (vis & InputMethodService.IME_ACTIVE) != 0);
   1191         updateNotificationIcons();
   1192         mInputMethodsPanel.setImeToken(token);
   1193         int res;
   1194         switch (backDisposition) {
   1195             case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
   1196                 res = R.drawable.ic_sysbar_back;
   1197                 break;
   1198             case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
   1199                 res = R.drawable.ic_sysbar_back_ime;
   1200                 break;
   1201             case InputMethodService.BACK_DISPOSITION_DEFAULT:
   1202             default:
   1203                 if ((vis & InputMethodService.IME_VISIBLE) != 0) {
   1204                     res = R.drawable.ic_sysbar_back_ime;
   1205                 } else {
   1206                     res = R.drawable.ic_sysbar_back;
   1207                 }
   1208                 break;
   1209         }
   1210         mBackButton.setImageResource(res);
   1211         if (FAKE_SPACE_BAR) {
   1212             mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0)
   1213                     ? View.VISIBLE : View.GONE);
   1214         }
   1215     }
   1216 
   1217     @Override
   1218     public void setHardKeyboardStatus(boolean available, boolean enabled) {
   1219         if (DEBUG) {
   1220             Slog.d(TAG, "Set hard keyboard status: available=" + available
   1221                     + ", enabled=" + enabled);
   1222         }
   1223         mInputMethodSwitchButton.setHardKeyboardStatus(available);
   1224         updateNotificationIcons();
   1225         mInputMethodsPanel.setHardKeyboardStatus(available, enabled);
   1226     }
   1227 
   1228     @Override
   1229     public void onHardKeyboardEnabledChange(boolean enabled) {
   1230         try {
   1231             mBarService.setHardKeyboardEnabled(enabled);
   1232         } catch (RemoteException ex) {
   1233         }
   1234     }
   1235 
   1236     private boolean isImmersive() {
   1237         try {
   1238             return ActivityManagerNative.getDefault().isTopActivityImmersive();
   1239             //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
   1240         } catch (RemoteException ex) {
   1241             // the end is nigh
   1242             return false;
   1243         }
   1244     }
   1245 
   1246     private void setAreThereNotifications() {
   1247         if (mNotificationPanel != null) {
   1248             mNotificationPanel.setClearable(mNotificationData.hasClearableItems());
   1249         }
   1250     }
   1251 
   1252     /**
   1253      * Cancel this notification and tell the status bar service about the failure. Hold no locks.
   1254      */
   1255     void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
   1256         removeNotification(key);
   1257         try {
   1258             mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
   1259         } catch (RemoteException ex) {
   1260             // The end is nigh.
   1261         }
   1262     }
   1263 
   1264     private void sendKey(KeyEvent key) {
   1265         try {
   1266             if (DEBUG) Slog.d(TAG, "injecting key event: " + key);
   1267             mWindowManager.injectInputEventNoWait(key);
   1268         } catch (RemoteException ex) {
   1269         }
   1270     }
   1271 
   1272     private View.OnClickListener mOnClickListener = new View.OnClickListener() {
   1273         public void onClick(View v) {
   1274             if (v == mRecentButton) {
   1275                 onClickRecentButton();
   1276             } else if (v == mInputMethodSwitchButton) {
   1277                 onClickInputMethodSwitchButton();
   1278             } else if (v == mCompatModeButton) {
   1279                 onClickCompatModeButton();
   1280             }
   1281         }
   1282     };
   1283 
   1284     public void onClickRecentButton() {
   1285         if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled);
   1286         if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
   1287             int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
   1288                 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
   1289             mHandler.removeMessages(msg);
   1290             mHandler.sendEmptyMessage(msg);
   1291         }
   1292     }
   1293 
   1294     public void onClickInputMethodSwitchButton() {
   1295         if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled);
   1296         int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ?
   1297                 MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL;
   1298         mHandler.removeMessages(msg);
   1299         mHandler.sendEmptyMessage(msg);
   1300     }
   1301 
   1302     public void onClickCompatModeButton() {
   1303         int msg = (mCompatModePanel.getVisibility() == View.GONE) ?
   1304                 MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL;
   1305         mHandler.removeMessages(msg);
   1306         mHandler.sendEmptyMessage(msg);
   1307     }
   1308 
   1309     public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
   1310         return new NotificationClicker(intent, pkg, tag, id);
   1311     }
   1312 
   1313     private class NotificationClicker implements View.OnClickListener {
   1314         private PendingIntent mIntent;
   1315         private String mPkg;
   1316         private String mTag;
   1317         private int mId;
   1318 
   1319         NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
   1320             mIntent = intent;
   1321             mPkg = pkg;
   1322             mTag = tag;
   1323             mId = id;
   1324         }
   1325 
   1326         public void onClick(View v) {
   1327             try {
   1328                 // The intent we are sending is for the application, which
   1329                 // won't have permission to immediately start an activity after
   1330                 // the user switches to home.  We know it is safe to do at this
   1331                 // point, so make sure new activity switches are now allowed.
   1332                 ActivityManagerNative.getDefault().resumeAppSwitches();
   1333                 // Also, notifications can be launched from the lock screen,
   1334                 // so dismiss the lock screen when the activity starts.
   1335                 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
   1336             } catch (RemoteException e) {
   1337             }
   1338 
   1339             if (mIntent != null) {
   1340                 int[] pos = new int[2];
   1341                 v.getLocationOnScreen(pos);
   1342                 Intent overlay = new Intent();
   1343                 overlay.setSourceBounds(
   1344                         new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
   1345                 try {
   1346                     mIntent.send(mContext, 0, overlay);
   1347 
   1348                 } catch (PendingIntent.CanceledException e) {
   1349                     // the stack trace isn't very helpful here.  Just log the exception message.
   1350                     Slog.w(TAG, "Sending contentIntent failed: " + e);
   1351                 }
   1352 
   1353                 KeyguardManager kgm =
   1354                     (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
   1355                 if (kgm != null) kgm.exitKeyguardSecurely(null);
   1356             }
   1357 
   1358             try {
   1359                 mBarService.onNotificationClick(mPkg, mTag, mId);
   1360             } catch (RemoteException ex) {
   1361                 // system process is dead if we're here.
   1362             }
   1363 
   1364             // close the shade if it was open
   1365             animateCollapse();
   1366             visibilityChanged(false);
   1367 
   1368             // If this click was on the intruder alert, hide that instead
   1369 //            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
   1370         }
   1371     }
   1372 
   1373     StatusBarNotification removeNotificationViews(IBinder key) {
   1374         NotificationData.Entry entry = mNotificationData.remove(key);
   1375         if (entry == null) {
   1376             Slog.w(TAG, "removeNotification for unknown key: " + key);
   1377             return null;
   1378         }
   1379         // Remove the expanded view.
   1380         ViewGroup rowParent = (ViewGroup)entry.row.getParent();
   1381         if (rowParent != null) rowParent.removeView(entry.row);
   1382 
   1383         if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) {
   1384             // must close the peek as well, since it's gone
   1385             mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
   1386         }
   1387         // Remove the icon.
   1388 //        ViewGroup iconParent = (ViewGroup)entry.icon.getParent();
   1389 //        if (iconParent != null) iconParent.removeView(entry.icon);
   1390         updateNotificationIcons();
   1391 
   1392         return entry.notification;
   1393     }
   1394 
   1395     private class NotificationTriggerTouchListener implements View.OnTouchListener {
   1396         VelocityTracker mVT;
   1397         float mInitialTouchX, mInitialTouchY;
   1398         int mTouchSlop;
   1399 
   1400         public NotificationTriggerTouchListener() {
   1401             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
   1402         }
   1403 
   1404         private Runnable mHiliteOnR = new Runnable() { public void run() {
   1405             mNotificationArea.setBackgroundResource(
   1406                 com.android.internal.R.drawable.list_selector_pressed_holo_dark);
   1407         }};
   1408         public void hilite(final boolean on) {
   1409             if (on) {
   1410                 mNotificationArea.postDelayed(mHiliteOnR, 100);
   1411             } else {
   1412                 mNotificationArea.removeCallbacks(mHiliteOnR);
   1413                 mNotificationArea.setBackgroundDrawable(null);
   1414             }
   1415         }
   1416 
   1417         public boolean onTouch(View v, MotionEvent event) {
   1418 //            Slog.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)",
   1419 //                        event.getX(),
   1420 //                        event.getY(),
   1421 //                        mInitialTouchX,
   1422 //                        mInitialTouchY));
   1423 
   1424             if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
   1425                 return true;
   1426             }
   1427 
   1428             final int action = event.getAction();
   1429             switch (action) {
   1430                 case MotionEvent.ACTION_DOWN:
   1431                     mVT = VelocityTracker.obtain();
   1432                     mInitialTouchX = event.getX();
   1433                     mInitialTouchY = event.getY();
   1434                     hilite(true);
   1435                     // fall through
   1436                 case MotionEvent.ACTION_OUTSIDE:
   1437                 case MotionEvent.ACTION_MOVE:
   1438                     // check for fling
   1439                     if (mVT != null) {
   1440                         mVT.addMovement(event);
   1441                         mVT.computeCurrentVelocity(1000); // pixels per second
   1442                         // require a little more oomph once we're already in peekaboo mode
   1443                         if (mVT.getYVelocity() < -mNotificationFlingVelocity) {
   1444                             animateExpand();
   1445                             visibilityChanged(true);
   1446                             hilite(false);
   1447                             mVT.recycle();
   1448                             mVT = null;
   1449                         }
   1450                     }
   1451                     return true;
   1452                 case MotionEvent.ACTION_UP:
   1453                 case MotionEvent.ACTION_CANCEL:
   1454                     hilite(false);
   1455                     if (mVT != null) {
   1456                         if (action == MotionEvent.ACTION_UP
   1457                          // was this a sloppy tap?
   1458                          && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop
   1459                          && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3)
   1460                          // dragging off the bottom doesn't count
   1461                          && (int)event.getY() < v.getBottom()) {
   1462                             animateExpand();
   1463                             visibilityChanged(true);
   1464                             v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
   1465                             v.playSoundEffect(SoundEffectConstants.CLICK);
   1466                         }
   1467 
   1468                         mVT.recycle();
   1469                         mVT = null;
   1470                         return true;
   1471                     }
   1472             }
   1473             return false;
   1474         }
   1475     }
   1476 
   1477     public void resetNotificationPeekFadeTimer() {
   1478         if (DEBUG) {
   1479             Slog.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY
   1480                 + "ms from now");
   1481         }
   1482         mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
   1483         mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK,
   1484                 NOTIFICATION_PEEK_FADE_DELAY);
   1485     }
   1486 
   1487     private class NotificationIconTouchListener implements View.OnTouchListener {
   1488         VelocityTracker mVT;
   1489         int mPeekIndex;
   1490         float mInitialTouchX, mInitialTouchY;
   1491         int mTouchSlop;
   1492 
   1493         public NotificationIconTouchListener() {
   1494             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
   1495         }
   1496 
   1497         public boolean onTouch(View v, MotionEvent event) {
   1498             boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
   1499             boolean panelShowing = mNotificationPanel.isShowing();
   1500             if (panelShowing) return false;
   1501 
   1502             int numIcons = mIconLayout.getChildCount();
   1503             int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth());
   1504             if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1;
   1505             else if (newPeekIndex < 0) newPeekIndex = 0;
   1506 
   1507             final int action = event.getAction();
   1508             switch (action) {
   1509                 case MotionEvent.ACTION_DOWN:
   1510                     mVT = VelocityTracker.obtain();
   1511                     mInitialTouchX = event.getX();
   1512                     mInitialTouchY = event.getY();
   1513                     mPeekIndex = -1;
   1514 
   1515                     // fall through
   1516                 case MotionEvent.ACTION_OUTSIDE:
   1517                 case MotionEvent.ACTION_MOVE:
   1518                     // peek and switch icons if necessary
   1519 
   1520                     if (newPeekIndex != mPeekIndex) {
   1521                         mPeekIndex = newPeekIndex;
   1522 
   1523                         if (DEBUG) Slog.d(TAG, "will peek at notification #" + mPeekIndex);
   1524                         Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
   1525                         peekMsg.arg1 = mPeekIndex;
   1526 
   1527                         mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
   1528 
   1529                         if (peeking) {
   1530                             // no delay if we're scrubbing left-right
   1531                             mHandler.sendMessage(peekMsg);
   1532                         } else {
   1533                             // wait for fling
   1534                             mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH);
   1535                         }
   1536                     }
   1537 
   1538                     // check for fling
   1539                     if (mVT != null) {
   1540                         mVT.addMovement(event);
   1541                         mVT.computeCurrentVelocity(1000); // pixels per second
   1542                         // require a little more oomph once we're already in peekaboo mode
   1543                         if (!panelShowing && (
   1544                                (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
   1545                             || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
   1546                             mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
   1547                             mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
   1548                             mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
   1549                             mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
   1550                         }
   1551                     }
   1552                     return true;
   1553                 case MotionEvent.ACTION_UP:
   1554                 case MotionEvent.ACTION_CANCEL:
   1555                     mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
   1556                     if (!peeking) {
   1557                         if (action == MotionEvent.ACTION_UP
   1558                                 // was this a sloppy tap?
   1559                                 && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop
   1560                                 && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3)
   1561                                 // dragging off the bottom doesn't count
   1562                                 && (int)event.getY() < v.getBottom()) {
   1563                             Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
   1564                             peekMsg.arg1 = mPeekIndex;
   1565                             mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
   1566                             mHandler.sendMessage(peekMsg);
   1567 
   1568                             v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
   1569                             v.playSoundEffect(SoundEffectConstants.CLICK);
   1570 
   1571                             peeking = true; // not technically true yet, but the next line will run
   1572                         }
   1573                     }
   1574 
   1575                     if (peeking) {
   1576                         resetNotificationPeekFadeTimer();
   1577                     }
   1578 
   1579                     mVT.recycle();
   1580                     mVT = null;
   1581                     return true;
   1582             }
   1583             return false;
   1584         }
   1585     }
   1586 
   1587     StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
   1588         if (DEBUG) {
   1589             Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
   1590         }
   1591         // Construct the icon.
   1592         final StatusBarIconView iconView = new StatusBarIconView(mContext,
   1593                 notification.pkg + "/0x" + Integer.toHexString(notification.id),
   1594                 notification.notification);
   1595         iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
   1596 
   1597         final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
   1598                     notification.notification.icon,
   1599                     notification.notification.iconLevel,
   1600                     notification.notification.number,
   1601                     notification.notification.tickerText);
   1602         if (!iconView.set(ic)) {
   1603             handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
   1604             return null;
   1605         }
   1606         // Construct the expanded view.
   1607         NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
   1608         if (!inflateViews(entry, mPile)) {
   1609             handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
   1610                     + notification);
   1611             return null;
   1612         }
   1613 
   1614         // Add the icon.
   1615         int pos = mNotificationData.add(entry);
   1616         if (DEBUG) {
   1617             Slog.d(TAG, "addNotificationViews: added at " + pos);
   1618         }
   1619         updateNotificationIcons();
   1620 
   1621         return iconView;
   1622     }
   1623 
   1624     private void reloadAllNotificationIcons() {
   1625         if (mIconLayout == null) return;
   1626         mIconLayout.removeAllViews();
   1627         updateNotificationIcons();
   1628     }
   1629 
   1630     private void updateNotificationIcons() {
   1631         // XXX: need to implement a new limited linear layout class
   1632         // to avoid removing & readding everything
   1633 
   1634         if (mIconLayout == null) return;
   1635 
   1636         // first, populate the main notification panel
   1637         loadNotificationPanel();
   1638 
   1639         final LinearLayout.LayoutParams params
   1640             = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
   1641 
   1642         // alternate behavior in DND mode
   1643         if (mNotificationDNDMode) {
   1644             if (mIconLayout.getChildCount() == 0) {
   1645                 final Notification dndNotification = new Notification.Builder(mContext)
   1646                     .setContentTitle(mContext.getText(R.string.notifications_off_title))
   1647                     .setContentText(mContext.getText(R.string.notifications_off_text))
   1648                     .setSmallIcon(R.drawable.ic_notification_dnd)
   1649                     .setOngoing(true)
   1650                     .getNotification();
   1651 
   1652                 final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd",
   1653                         dndNotification);
   1654                 iconView.setImageResource(R.drawable.ic_notification_dnd);
   1655                 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
   1656                 iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0);
   1657 
   1658                 mNotificationDNDDummyEntry = new NotificationData.Entry(
   1659                         null,
   1660                         new StatusBarNotification("", 0, "", 0, 0, dndNotification),
   1661                         iconView);
   1662 
   1663                 mIconLayout.addView(iconView, params);
   1664             }
   1665 
   1666             return;
   1667         } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) {
   1668             // if icons are disabled but we're not in DND mode, this is probably Setup and we should
   1669             // just leave the area totally empty
   1670             return;
   1671         }
   1672 
   1673         int N = mNotificationData.size();
   1674 
   1675         if (DEBUG) {
   1676             Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
   1677         }
   1678 
   1679         ArrayList<View> toShow = new ArrayList<View>();
   1680 
   1681         // Extra Special Icons
   1682         // The IME switcher and compatibility mode icons take the place of notifications. You didn't
   1683         // need to see all those new emails, did you?
   1684         int maxNotificationIconsCount = mMaxNotificationIcons;
   1685         if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount --;
   1686         if (mCompatModeButton.getVisibility()        != View.GONE) maxNotificationIconsCount --;
   1687 
   1688         for (int i=0; i< maxNotificationIconsCount; i++) {
   1689             if (i>=N) break;
   1690             toShow.add(mNotificationData.get(N-i-1).icon);
   1691         }
   1692 
   1693         ArrayList<View> toRemove = new ArrayList<View>();
   1694         for (int i=0; i<mIconLayout.getChildCount(); i++) {
   1695             View child = mIconLayout.getChildAt(i);
   1696             if (!toShow.contains(child)) {
   1697                 toRemove.add(child);
   1698             }
   1699         }
   1700 
   1701         for (View remove : toRemove) {
   1702             mIconLayout.removeView(remove);
   1703         }
   1704 
   1705         for (int i=0; i<toShow.size(); i++) {
   1706             View v = toShow.get(i);
   1707             v.setPadding(mIconHPadding, 0, mIconHPadding, 0);
   1708             if (v.getParent() == null) {
   1709                 mIconLayout.addView(v, i, params);
   1710             }
   1711         }
   1712     }
   1713 
   1714     private void loadNotificationPanel() {
   1715         int N = mNotificationData.size();
   1716 
   1717         ArrayList<View> toShow = new ArrayList<View>();
   1718 
   1719         for (int i=0; i<N; i++) {
   1720             View row = mNotificationData.get(N-i-1).row;
   1721             toShow.add(row);
   1722         }
   1723 
   1724         ArrayList<View> toRemove = new ArrayList<View>();
   1725         for (int i=0; i<mPile.getChildCount(); i++) {
   1726             View child = mPile.getChildAt(i);
   1727             if (!toShow.contains(child)) {
   1728                 toRemove.add(child);
   1729             }
   1730         }
   1731 
   1732         for (View remove : toRemove) {
   1733             mPile.removeView(remove);
   1734         }
   1735 
   1736         for (int i=0; i<toShow.size(); i++) {
   1737             View v = toShow.get(i);
   1738             if (v.getParent() == null) {
   1739                 mPile.addView(v, N-1-i); // the notification panel has newest at the bottom
   1740             }
   1741         }
   1742 
   1743         mNotificationPanel.setNotificationCount(N);
   1744     }
   1745 
   1746     void workAroundBadLayerDrawableOpacity(View v) {
   1747         Drawable bgd = v.getBackground();
   1748         if (!(bgd instanceof LayerDrawable)) return;
   1749 
   1750         LayerDrawable d = (LayerDrawable) bgd;
   1751         v.setBackgroundDrawable(null);
   1752         d.setOpacity(PixelFormat.TRANSLUCENT);
   1753         v.setBackgroundDrawable(d);
   1754     }
   1755 
   1756     private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
   1757         StatusBarNotification sbn = entry.notification;
   1758         RemoteViews remoteViews = sbn.notification.contentView;
   1759         if (remoteViews == null) {
   1760             return false;
   1761         }
   1762 
   1763         // create the row view
   1764         LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
   1765                 Context.LAYOUT_INFLATER_SERVICE);
   1766         View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
   1767         workAroundBadLayerDrawableOpacity(row);
   1768         View vetoButton = updateNotificationVetoButton(row, entry.notification);
   1769         vetoButton.setContentDescription(mContext.getString(
   1770                 R.string.accessibility_remove_notification));
   1771 
   1772         // the large icon
   1773         ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
   1774         if (sbn.notification.largeIcon != null) {
   1775             largeIcon.setImageBitmap(sbn.notification.largeIcon);
   1776             largeIcon.setContentDescription(sbn.notification.tickerText);
   1777         } else {
   1778             largeIcon.getLayoutParams().width = 0;
   1779             largeIcon.setVisibility(View.INVISIBLE);
   1780         }
   1781         largeIcon.setContentDescription(sbn.notification.tickerText);
   1782 
   1783         // bind the click event to the content area
   1784         ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
   1785         // XXX: update to allow controls within notification views
   1786         content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
   1787 //        content.setOnFocusChangeListener(mFocusChangeListener);
   1788         PendingIntent contentIntent = sbn.notification.contentIntent;
   1789         if (contentIntent != null) {
   1790             final View.OnClickListener listener = new NotificationClicker(
   1791                     contentIntent, sbn.pkg, sbn.tag, sbn.id);
   1792             largeIcon.setOnClickListener(listener);
   1793             content.setOnClickListener(listener);
   1794         } else {
   1795             largeIcon.setOnClickListener(null);
   1796             content.setOnClickListener(null);
   1797         }
   1798 
   1799         View expanded = null;
   1800         Exception exception = null;
   1801         try {
   1802             expanded = remoteViews.apply(mContext, content);
   1803         }
   1804         catch (RuntimeException e) {
   1805             exception = e;
   1806         }
   1807         if (expanded == null) {
   1808             final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
   1809             Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
   1810             return false;
   1811         } else {
   1812             content.addView(expanded);
   1813             row.setDrawingCacheEnabled(true);
   1814         }
   1815 
   1816         applyLegacyRowBackground(sbn, content);
   1817 
   1818         entry.row = row;
   1819         entry.content = content;
   1820         entry.expanded = expanded;
   1821         entry.largeIcon = largeIcon;
   1822 
   1823         return true;
   1824     }
   1825 
   1826     void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
   1827         if (sbn.notification.contentView.getLayoutId() !=
   1828                 com.android.internal.R.layout.status_bar_latest_event_content) {
   1829             int version = 0;
   1830             try {
   1831                 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0);
   1832                 version = info.targetSdkVersion;
   1833             } catch (NameNotFoundException ex) {
   1834                 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex);
   1835             }
   1836             if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
   1837                 content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
   1838             } else {
   1839                 content.setBackgroundResource(R.drawable.notification_row_bg);
   1840             }
   1841         }
   1842     }
   1843 
   1844     public void clearAll() {
   1845         try {
   1846             mBarService.onClearAllNotifications();
   1847         } catch (RemoteException ex) {
   1848             // system process is dead if we're here.
   1849         }
   1850         animateCollapse();
   1851         visibilityChanged(false);
   1852     }
   1853 
   1854     public void toggleRecentApps() {
   1855         int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
   1856                 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
   1857         mHandler.removeMessages(msg);
   1858         mHandler.sendEmptyMessage(msg);
   1859     }
   1860 
   1861     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
   1862         public void onReceive(Context context, Intent intent) {
   1863             String action = intent.getAction();
   1864             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
   1865                 || Intent.ACTION_SCREEN_OFF.equals(action)) {
   1866                 boolean excludeRecents = false;
   1867                 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
   1868                     String reason = intent.getStringExtra("reason");
   1869                     if (reason != null) {
   1870                         excludeRecents = reason.equals("recentapps");
   1871                     }
   1872                 }
   1873                 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
   1874                     // If we're turning the screen off, we want to hide the
   1875                     // recents panel with no animation
   1876                     // TODO: hide other things, like the notification tray,
   1877                     // with no animation as well
   1878                     mRecentsPanel.show(false, false);
   1879                     excludeRecents = true;
   1880                 }
   1881                 animateCollapse(excludeRecents);
   1882             }
   1883         }
   1884     };
   1885 
   1886     public class TouchOutsideListener implements View.OnTouchListener {
   1887         private int mMsg;
   1888         private StatusBarPanel mPanel;
   1889 
   1890         public TouchOutsideListener(int msg, StatusBarPanel panel) {
   1891             mMsg = msg;
   1892             mPanel = panel;
   1893         }
   1894 
   1895         public boolean onTouch(View v, MotionEvent ev) {
   1896             final int action = ev.getAction();
   1897             if (action == MotionEvent.ACTION_OUTSIDE
   1898                     || (action == MotionEvent.ACTION_DOWN
   1899                         && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
   1900                 mHandler.removeMessages(mMsg);
   1901                 mHandler.sendEmptyMessage(mMsg);
   1902                 return true;
   1903             }
   1904             return false;
   1905         }
   1906     }
   1907 
   1908     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   1909         pw.print("mDisabled=0x");
   1910         pw.println(Integer.toHexString(mDisabled));
   1911         pw.println("mNetworkController:");
   1912         mNetworkController.dump(fd, pw, args);
   1913     }
   1914 }
   1915 
   1916 
   1917