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