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