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