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