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