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