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