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