TabletStatusBar.java revision ddbba42b812ab397a75db78aba6b48fecf0e31f6
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.Resources;
35import android.graphics.PixelFormat;
36import android.graphics.Rect;
37import android.graphics.drawable.Drawable;
38import android.graphics.drawable.LayerDrawable;
39import android.os.Handler;
40import android.os.IBinder;
41import android.os.Message;
42import android.os.RemoteException;
43import android.text.TextUtils;
44import android.util.Slog;
45import android.view.animation.Animation;
46import android.view.animation.AnimationUtils;
47import android.view.Gravity;
48import android.view.LayoutInflater;
49import android.view.MotionEvent;
50import android.view.VelocityTracker;
51import android.view.View;
52import android.view.ViewConfiguration;
53import android.view.ViewGroup;
54import android.view.WindowManager;
55import android.view.WindowManagerImpl;
56import android.widget.FrameLayout;
57import android.widget.ImageView;
58import android.widget.LinearLayout;
59import android.widget.RemoteViews;
60import android.widget.ScrollView;
61import android.widget.TextSwitcher;
62import android.widget.TextView;
63
64import com.android.internal.statusbar.StatusBarIcon;
65import com.android.internal.statusbar.StatusBarNotification;
66
67import com.android.systemui.R;
68import com.android.systemui.statusbar.*;
69import com.android.systemui.statusbar.policy.BatteryController;
70import com.android.systemui.statusbar.policy.NetworkController;
71import com.android.systemui.recent.RecentApplicationsActivity;
72
73public class TabletStatusBar extends StatusBar {
74    public static final boolean DEBUG = false;
75    public static final String TAG = "TabletStatusBar";
76
77    public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
78    public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
79    public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002;
80    public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003;
81    public static final int MSG_OPEN_RECENTS_PANEL = 1020;
82    public static final int MSG_CLOSE_RECENTS_PANEL = 1021;
83    public static final int MSG_HIDE_SHADOWS = 1030;
84    public static final int MSG_SHOW_SHADOWS = 1031;
85    public static final int MSG_RESTORE_SHADOWS = 1032;
86
87    private static final int MAX_IMAGE_LEVEL = 10000;
88    private static final boolean USE_2D_RECENTS = true;
89
90    public static final int LIGHTS_ON_DELAY = 5000;
91
92    int mIconSize;
93
94    H mHandler = new H();
95
96    // tracking all current notifications
97    private NotificationData mNotns = new NotificationData();
98
99    TabletStatusBarView mStatusBarView;
100    View mNotificationArea;
101    View mNotificationTrigger;
102    NotificationIconArea mNotificationIconArea;
103    View mNavigationArea;
104
105    View mBackButton;
106    View mHomeButton;
107    View mMenuButton;
108    View mRecentButton;
109
110    InputMethodButton mInputMethodButton;
111
112    NotificationPanel mNotificationPanel;
113    NotificationPeekPanel mNotificationPeekWindow;
114    ViewGroup mNotificationPeekRow;
115    int mNotificationPeekIndex;
116    LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight;
117
118    int mNotificationPeekTapDuration;
119    int mNotificationFlingVelocity;
120
121    ViewGroup mPile;
122
123    BatteryController mBatteryController;
124    NetworkController mNetworkController;
125
126    View mBarContents;
127
128    // lights out support
129    View mBackShadow, mHomeShadow, mRecentShadow, mMenuShadow, mNotificationShadow;
130    ShadowController mShadowController;
131
132    NotificationIconArea.IconLayout mIconLayout;
133
134    TabletTicker mTicker;
135
136    // for disabling the status bar
137    int mDisabled = 0;
138
139    boolean mNotificationsOn = true;
140    private RecentAppsPanel mRecentsPanel;
141
142    protected void addPanelWindows() {
143        final Context context = mContext;
144
145        final Resources res = context.getResources();
146        final int barHeight= res.getDimensionPixelSize(
147            com.android.internal.R.dimen.status_bar_height);
148
149        // Notification Panel
150        mNotificationPanel = (NotificationPanel)View.inflate(context,
151                R.layout.sysbar_panel_notifications, null);
152        mNotificationPanel.setVisibility(View.GONE);
153        mNotificationPanel.setOnTouchListener(
154                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel));
155
156        // the battery and network icons
157        mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery));
158        mBatteryController.addLabelView(
159                (TextView)mNotificationPanel.findViewById(R.id.battery_text));
160        mNetworkController.addCombinedSignalIconView(
161                (ImageView)mNotificationPanel.findViewById(R.id.network));
162        mNetworkController.addLabelView(
163                (TextView)mNotificationPanel.findViewById(R.id.network_text));
164
165        mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
166
167        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
168                ViewGroup.LayoutParams.MATCH_PARENT,
169                ViewGroup.LayoutParams.MATCH_PARENT,
170                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
171                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
172                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
173                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
174                PixelFormat.TRANSLUCENT);
175        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
176        lp.setTitle("NotificationPanel");
177
178        WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
179
180        // Notification preview window
181        mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context,
182                R.layout.sysbar_panel_notification_peek, null);
183        mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content);
184        mNotificationPeekWindow.setVisibility(View.GONE);
185        mNotificationPeekWindow.setOnTouchListener(
186                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPeekWindow));
187        mNotificationPeekScrubRight = new LayoutTransition();
188        mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING,
189                ObjectAnimator.ofInt(null, "left", -512, 0));
190        mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING,
191                ObjectAnimator.ofInt(null, "left", -512, 0));
192        mNotificationPeekScrubRight.setDuration(500);
193
194        mNotificationPeekScrubLeft = new LayoutTransition();
195        mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING,
196                ObjectAnimator.ofInt(null, "left", 512, 0));
197        mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING,
198                ObjectAnimator.ofInt(null, "left", 512, 0));
199        mNotificationPeekScrubLeft.setDuration(500);
200
201        // XXX: setIgnoreChildren?
202        lp = new WindowManager.LayoutParams(
203                512, // ViewGroup.LayoutParams.WRAP_CONTENT,
204                ViewGroup.LayoutParams.WRAP_CONTENT,
205                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
206                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
207                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
208                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
209                PixelFormat.TRANSLUCENT);
210        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
211        lp.setTitle("NotificationPeekWindow");
212        lp.windowAnimations = com.android.internal.R.style.Animation_Toast;
213
214        WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp);
215
216        // Recents Panel
217        if (USE_2D_RECENTS) {
218            mRecentsPanel = (RecentAppsPanel) View.inflate(context, R.layout.sysbar_panel_recent,
219                    null);
220            mRecentsPanel.setVisibility(View.GONE);
221            mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
222                    mRecentsPanel));
223            mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel);
224
225            lp = new WindowManager.LayoutParams(
226                    ViewGroup.LayoutParams.WRAP_CONTENT,
227                    ViewGroup.LayoutParams.WRAP_CONTENT,
228                    WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
229                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
230                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
231                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
232                    PixelFormat.TRANSLUCENT);
233            lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
234            lp.setTitle("RecentsPanel");
235            lp.windowAnimations = R.style.Animation_RecentPanel;
236
237            WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
238            mRecentsPanel.setBar(this);
239        }
240    }
241
242    @Override
243    public void start() {
244        super.start(); // will add the main bar view
245    }
246
247    protected View makeStatusBarView() {
248        final Context context = mContext;
249        final Resources res = context.getResources();
250
251        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
252
253        final TabletStatusBarView sb = (TabletStatusBarView)View.inflate(
254                context, R.layout.status_bar, null);
255        mStatusBarView = sb;
256
257        sb.setHandler(mHandler);
258
259        mBarContents = sb.findViewById(R.id.bar_contents);
260
261        // the whole right-hand side of the bar
262        mNotificationArea = sb.findViewById(R.id.notificationArea);
263
264        // the button to open the notification area
265        mNotificationTrigger = sb.findViewById(R.id.notificationTrigger);
266        mNotificationTrigger.setOnClickListener(mOnClickListener);
267
268        // the more notifications icon
269        mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
270
271        // where the icons go
272        mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
273        mIconLayout.setOnTouchListener(new NotificationIconTouchListener());
274
275        ViewConfiguration vc = ViewConfiguration.get(context);
276        mNotificationPeekTapDuration = vc.getTapTimeout();
277        mNotificationFlingVelocity = 300; // px/s
278
279        mTicker = new TabletTicker(context);
280
281        // The icons
282        mBatteryController = new BatteryController(mContext);
283        mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
284        mNetworkController = new NetworkController(mContext);
285        mNetworkController.addCombinedSignalIconView((ImageView)sb.findViewById(R.id.network));
286
287        // The navigation buttons
288        mNavigationArea = sb.findViewById(R.id.navigationArea);
289        mBackButton = mNavigationArea.findViewById(R.id.back);
290        mHomeButton = mNavigationArea.findViewById(R.id.home);
291        mMenuButton = mNavigationArea.findViewById(R.id.menu);
292        mRecentButton = mNavigationArea.findViewById(R.id.recent_apps);
293        Slog.d(TAG, "rec=" + mRecentButton + ", listener=" + mOnClickListener);
294        mRecentButton.setOnClickListener(mOnClickListener);
295
296        // The bar contents buttons
297        mInputMethodButton = (InputMethodButton) sb.findViewById(R.id.imeButton);
298
299        // "shadows" of the status bar features, for lights-out mode
300        mBackShadow = sb.findViewById(R.id.back_shadow);
301        mHomeShadow = sb.findViewById(R.id.home_shadow);
302        mRecentShadow = sb.findViewById(R.id.recent_shadow);
303        mMenuShadow = sb.findViewById(R.id.menu_shadow);
304        mNotificationShadow = sb.findViewById(R.id.notification_shadow);
305
306        mShadowController = new ShadowController(false);
307        mShadowController.add(mBackButton, mBackShadow);
308        mShadowController.add(mHomeButton, mHomeShadow);
309        mShadowController.add(mRecentButton, mRecentShadow);
310        mShadowController.add(mMenuButton, mMenuShadow);
311        mShadowController.add(mNotificationArea, mNotificationShadow);
312
313        // set the initial view visibility
314        setAreThereNotifications();
315        refreshNotificationTrigger();
316
317        // Add the windows
318        addPanelWindows();
319
320        mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
321        mPile.removeAllViews();
322
323        ScrollView scroller = (ScrollView)mPile.getParent();
324        scroller.setFillViewport(true);
325
326        return sb;
327    }
328
329    protected int getStatusBarGravity() {
330        return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
331    }
332
333    private class H extends Handler {
334        public void handleMessage(Message m) {
335            switch (m.what) {
336                case MSG_OPEN_NOTIFICATION_PEEK:
337                    if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1);
338                    if (m.arg1 >= 0) {
339                        final int N = mNotns.size();
340                        if (mNotificationPeekIndex < N) {
341                            NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex);
342                            entry.icon.setBackgroundColor(0);
343                        }
344
345                        final int peekIndex = m.arg1;
346                        if (peekIndex < N) {
347                            Slog.d(TAG, "loading peek: " + peekIndex);
348                            NotificationData.Entry entry = mNotns.get(N-1-peekIndex);
349                            NotificationData.Entry copy = new NotificationData.Entry(
350                                    entry.key,
351                                    entry.notification,
352                                    entry.icon);
353                            inflateViews(copy, mNotificationPeekRow);
354
355                            entry.icon.setBackgroundColor(0x20FFFFFF);
356
357//                          mNotificationPeekRow.setLayoutTransition(
358//                              peekIndex < mNotificationPeekIndex
359//                                  ? mNotificationPeekScrubLeft
360//                                  : mNotificationPeekScrubRight);
361
362                            mNotificationPeekRow.removeAllViews();
363                            mNotificationPeekRow.addView(copy.row);
364
365                            mNotificationPeekWindow.setVisibility(View.VISIBLE);
366                            mNotificationPanel.setVisibility(View.GONE);
367
368                            mNotificationPeekIndex = peekIndex;
369                        }
370                    }
371                    break;
372                case MSG_CLOSE_NOTIFICATION_PEEK:
373                    if (DEBUG) Slog.d(TAG, "closing notification peek window");
374                    mNotificationPeekWindow.setVisibility(View.GONE);
375                    mNotificationPeekRow.removeAllViews();
376                    final int N = mNotns.size();
377                    if (mNotificationPeekIndex < N) {
378                        NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex);
379                        entry.icon.setBackgroundColor(0);
380                    }
381                    break;
382                case MSG_OPEN_NOTIFICATION_PANEL:
383                    if (DEBUG) Slog.d(TAG, "opening notifications panel");
384                    if (mNotificationPanel.getVisibility() == View.GONE) {
385                        mNotificationPeekWindow.setVisibility(View.GONE);
386                        mNotificationPanel.setVisibility(View.VISIBLE);
387                        // synchronize with current shadow state
388                        mShadowController.hideElement(mNotificationArea);
389                    }
390                    break;
391                case MSG_CLOSE_NOTIFICATION_PANEL:
392                    if (DEBUG) Slog.d(TAG, "closing notifications panel");
393                    if (mNotificationPanel.getVisibility() == View.VISIBLE) {
394                        mNotificationPanel.setVisibility(View.GONE);
395                        // synchronize with current shadow state
396                        mShadowController.showElement(mNotificationArea);
397                    }
398                    break;
399                case MSG_OPEN_RECENTS_PANEL:
400                    if (DEBUG) Slog.d(TAG, "opening recents panel");
401                    if (mRecentsPanel != null) mRecentsPanel.setVisibility(View.VISIBLE);
402                    break;
403                case MSG_CLOSE_RECENTS_PANEL:
404                    if (DEBUG) Slog.d(TAG, "closing recents panel");
405                    if (mRecentsPanel != null) mRecentsPanel.setVisibility(View.GONE);
406                    break;
407                case MSG_HIDE_SHADOWS:
408                    if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)");
409                    mShadowController.hideAllShadows();
410                    break;
411                case MSG_SHOW_SHADOWS:
412                    if (DEBUG) Slog.d(TAG, "showing shadows (lights out)");
413                    animateCollapse();
414                    mShadowController.showAllShadows();
415                    break;
416                case MSG_RESTORE_SHADOWS:
417                    if (DEBUG) Slog.d(TAG, "quickly re-showing shadows if appropriate");
418                    mShadowController.refresh();
419                    break;
420            }
421        }
422    }
423
424    public void refreshNotificationTrigger() {
425        /*
426        if (mNotificationTrigger == null) return;
427
428        int resId;
429        boolean panel = (mNotificationPanel != null
430                && mNotificationPanel.getVisibility() == View.VISIBLE);
431        if (!mNotificationsOn) {
432            resId = R.drawable.ic_sysbar_noti_dnd;
433        } else if (mNotns.size() > 0) {
434            resId = panel ? R.drawable.ic_sysbar_noti_avail_open : R.drawable.ic_sysbar_noti_avail;
435        } else {
436            resId = panel ? R.drawable.ic_sysbar_noti_none_open : R.drawable.ic_sysbar_noti_none;
437        }
438        //mNotificationTrigger.setImageResource(resId);
439        */
440    }
441
442    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
443        if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon);
444    }
445
446    public void updateIcon(String slot, int index, int viewIndex,
447            StatusBarIcon old, StatusBarIcon icon) {
448        if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon);
449    }
450
451    public void removeIcon(String slot, int index, int viewIndex) {
452        if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")");
453    }
454
455    public void addNotification(IBinder key, StatusBarNotification notification) {
456        if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
457        addNotificationViews(key, notification);
458
459        boolean immersive = false;
460        try {
461            immersive = ActivityManagerNative.getDefault().isTopActivityImmersive();
462            //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
463        } catch (RemoteException ex) {
464        }
465        if (false && immersive) {
466            // TODO: immersive mode popups for tablet
467        } else if (notification.notification.fullScreenIntent != null) {
468            // not immersive & a full-screen alert should be shown
469            Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;"
470                    + " sending fullScreenIntent");
471            try {
472                notification.notification.fullScreenIntent.send();
473            } catch (PendingIntent.CanceledException e) {
474            }
475        } else {
476            tick(notification);
477        }
478
479        setAreThereNotifications();
480    }
481
482    public void updateNotification(IBinder key, StatusBarNotification notification) {
483        if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ") // TODO");
484
485        final NotificationData.Entry oldEntry = mNotns.findByKey(key);
486        if (oldEntry == null) {
487            Slog.w(TAG, "updateNotification for unknown key: " + key);
488            return;
489        }
490
491        final StatusBarNotification oldNotification = oldEntry.notification;
492        final RemoteViews oldContentView = oldNotification.notification.contentView;
493
494        final RemoteViews contentView = notification.notification.contentView;
495
496        if (false) {
497            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
498                    + " ongoing=" + oldNotification.isOngoing()
499                    + " expanded=" + oldEntry.expanded
500                    + " contentView=" + oldContentView);
501            Slog.d(TAG, "new notification: when=" + notification.notification.when
502                    + " ongoing=" + oldNotification.isOngoing()
503                    + " contentView=" + contentView);
504        }
505
506        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
507        // didn't change.
508        if (notification.notification.when == oldNotification.notification.when
509                && notification.isOngoing() == oldNotification.isOngoing()
510                && oldEntry.expanded != null
511                && contentView != null
512                && oldContentView != null
513                && contentView.getPackage() != null
514                && oldContentView.getPackage() != null
515                && oldContentView.getPackage().equals(contentView.getPackage())
516                && oldContentView.getLayoutId() == contentView.getLayoutId()) {
517            if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
518            oldEntry.notification = notification;
519            try {
520                // Reapply the RemoteViews
521                contentView.reapply(mContext, oldEntry.content);
522                // update the contentIntent
523                final PendingIntent contentIntent = notification.notification.contentIntent;
524                if (contentIntent != null) {
525                    oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent,
526                                notification.pkg, notification.tag, notification.id));
527                } else {
528                    oldEntry.content.setOnClickListener(null);
529                }
530                // Update the icon.
531                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
532                        notification.notification.icon, notification.notification.iconLevel,
533                        notification.notification.number);
534                if (!oldEntry.icon.set(ic)) {
535                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
536                    return;
537                }
538            }
539            catch (RuntimeException e) {
540                // It failed to add cleanly.  Log, and remove the view from the panel.
541                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
542                removeNotificationViews(key);
543                addNotificationViews(key, notification);
544            }
545        } else {
546            if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
547            removeNotificationViews(key);
548            addNotificationViews(key, notification);
549        }
550        // TODO: ticker; immersive mode
551
552        setAreThereNotifications();
553    }
554
555    public void removeNotification(IBinder key) {
556        if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO");
557        removeNotificationViews(key);
558        setAreThereNotifications();
559    }
560
561    public void disable(int state) {
562        int old = mDisabled;
563        int diff = state ^ old;
564        mDisabled = state;
565
566        // act accordingly
567        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
568            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
569                Slog.d(TAG, "DISABLE_EXPAND: yes");
570                animateCollapse();
571            }
572        }
573        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
574            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
575                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
576                // synchronize with current shadow state
577                mShadowController.hideElement(mNotificationArea);
578                mTicker.halt();
579            } else {
580                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
581                // synchronize with current shadow state
582                mShadowController.showElement(mNotificationArea);
583            }
584        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
585            if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
586                mTicker.halt();
587            }
588        }
589        if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) {
590            if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) {
591                Slog.d(TAG, "DISABLE_NAVIGATION: yes");
592                mNavigationArea.setVisibility(View.GONE);
593            } else {
594                Slog.d(TAG, "DISABLE_NAVIGATION: no");
595                mNavigationArea.setVisibility(View.VISIBLE);
596            }
597        }
598    }
599
600    private boolean hasTicker(Notification n) {
601        return n.tickerView != null || !TextUtils.isEmpty(n.tickerText);
602    }
603
604    private void tick(StatusBarNotification n) {
605        // Don't show the ticker when the windowshade is open.
606        if (mNotificationPanel.getVisibility() == View.VISIBLE) {
607            return;
608        }
609        // Show the ticker if one is requested. Also don't do this
610        // until status bar window is attached to the window manager,
611        // because...  well, what's the point otherwise?  And trying to
612        // run a ticker without being attached will crash!
613        if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
614            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
615                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
616                mTicker.add(n);
617            }
618        }
619    }
620
621    public void animateExpand() {
622        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
623        mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
624    }
625
626    public void animateCollapse() {
627        mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
628        mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
629        mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
630        mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
631    }
632
633    // called by StatusBar
634    @Override
635    public void setLightsOn(boolean on) {
636        mHandler.removeMessages(MSG_SHOW_SHADOWS);
637        mHandler.removeMessages(MSG_HIDE_SHADOWS);
638        mHandler.sendEmptyMessage(on ? MSG_HIDE_SHADOWS : MSG_SHOW_SHADOWS);
639    }
640
641    public void setMenuKeyVisible(boolean visible) {
642        if (DEBUG) {
643            Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button");
644        }
645        mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE);
646    }
647
648    public void setIMEButtonVisible(boolean visible) {
649        if (DEBUG) {
650            Slog.d(TAG, (visible?"showing":"hiding") + " the IME button");
651        }
652        mInputMethodButton.setIMEButtonVisible(visible);
653    }
654
655    private void setAreThereNotifications() {
656        final boolean hasClearable = mNotns.hasClearableItems();
657
658        //Slog.d(TAG, "setAreThereNotifications hasClerable=" + hasClearable);
659
660        /*
661        mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE);
662        mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE);
663
664        if (ongoing || latest) {
665            mNoNotificationsTitle.setVisibility(View.GONE);
666        } else {
667            mNoNotificationsTitle.setVisibility(View.VISIBLE);
668        }
669        */
670    }
671
672    /**
673     * Cancel this notification and tell the status bar service about the failure. Hold no locks.
674     */
675    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
676        removeNotification(key);
677        try {
678            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
679        } catch (RemoteException ex) {
680            // The end is nigh.
681        }
682    }
683
684    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
685        public void onClick(View v) {
686            if (v == mNotificationTrigger) {
687                onClickNotificationTrigger();
688            } else if (v == mRecentButton) {
689                onClickRecentButton();
690            }
691        }
692    };
693
694    public void onClickNotificationTrigger() {
695        if (DEBUG) Slog.d(TAG, "clicked notification icons; disabled=" + mDisabled);
696        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
697            if (!mNotificationsOn) {
698                mNotificationsOn = true;
699                mIconLayout.setVisibility(View.VISIBLE); // TODO: animation
700                refreshNotificationTrigger();
701            } else {
702                int msg = (mNotificationPanel.getVisibility() == View.GONE)
703                    ? MSG_OPEN_NOTIFICATION_PANEL
704                    : MSG_CLOSE_NOTIFICATION_PANEL;
705                mHandler.removeMessages(msg);
706                mHandler.sendEmptyMessage(msg);
707            }
708        }
709    }
710
711    public void onClickRecentButton() {
712        if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled);
713        if (mRecentsPanel == null) {
714            Intent intent = new Intent();
715            intent.setClass(mContext, RecentApplicationsActivity.class);
716            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
717                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
718            mContext.startActivity(intent);
719        } else {
720            if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
721                int msg = (mRecentsPanel.getVisibility() == View.GONE)
722                    ? MSG_OPEN_RECENTS_PANEL
723                    : MSG_CLOSE_RECENTS_PANEL;
724                mHandler.removeMessages(msg);
725                mHandler.sendEmptyMessage(msg);
726            }
727        }
728    }
729
730    private class NotificationClicker implements View.OnClickListener {
731        private PendingIntent mIntent;
732        private String mPkg;
733        private String mTag;
734        private int mId;
735
736        NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
737            mIntent = intent;
738            mPkg = pkg;
739            mTag = tag;
740            mId = id;
741        }
742
743        public void onClick(View v) {
744            try {
745                // The intent we are sending is for the application, which
746                // won't have permission to immediately start an activity after
747                // the user switches to home.  We know it is safe to do at this
748                // point, so make sure new activity switches are now allowed.
749                ActivityManagerNative.getDefault().resumeAppSwitches();
750            } catch (RemoteException e) {
751            }
752
753            if (mIntent != null) {
754                int[] pos = new int[2];
755                v.getLocationOnScreen(pos);
756                Intent overlay = new Intent();
757                overlay.setSourceBounds(
758                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
759                try {
760                    mIntent.send(mContext, 0, overlay);
761                } catch (PendingIntent.CanceledException e) {
762                    // the stack trace isn't very helpful here.  Just log the exception message.
763                    Slog.w(TAG, "Sending contentIntent failed: " + e);
764                }
765            }
766
767            try {
768                mBarService.onNotificationClick(mPkg, mTag, mId);
769            } catch (RemoteException ex) {
770                // system process is dead if we're here.
771            }
772
773            // close the shade if it was open
774            animateCollapse();
775
776            // If this click was on the intruder alert, hide that instead
777//            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
778        }
779    }
780
781    StatusBarNotification removeNotificationViews(IBinder key) {
782        NotificationData.Entry entry = mNotns.remove(key);
783        if (entry == null) {
784            Slog.w(TAG, "removeNotification for unknown key: " + key);
785            return null;
786        }
787        // Remove the expanded view.
788        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
789        if (rowParent != null) rowParent.removeView(entry.row);
790        // Remove the icon.
791//        ViewGroup iconParent = (ViewGroup)entry.icon.getParent();
792//        if (iconParent != null) iconParent.removeView(entry.icon);
793        refreshIcons();
794
795        return entry.notification;
796    }
797
798    private class NotificationIconTouchListener implements View.OnTouchListener {
799        VelocityTracker mVT;
800
801        public NotificationIconTouchListener() {
802        }
803
804        public boolean onTouch(View v, MotionEvent event) {
805            boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
806            boolean panelShowing = mNotificationPanel.getVisibility() != View.GONE;
807            if (panelShowing) return false;
808
809            switch (event.getAction()) {
810                case MotionEvent.ACTION_DOWN:
811                    mVT = VelocityTracker.obtain();
812
813                    // fall through
814                case MotionEvent.ACTION_OUTSIDE:
815                case MotionEvent.ACTION_MOVE:
816                    // peek and switch icons if necessary
817                    int numIcons = mIconLayout.getChildCount();
818                    int peekIndex =
819                            (int)((float)event.getX() * numIcons / mIconLayout.getWidth());
820                    if (peekIndex > numIcons - 1) peekIndex = numIcons - 1;
821                    else if (peekIndex < 0) peekIndex = 0;
822
823                    if (!peeking || mNotificationPeekIndex != peekIndex) {
824                        if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex);
825                        Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
826                        peekMsg.arg1 = peekIndex;
827
828                        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
829
830                        // no delay if we're scrubbing left-right
831                        mHandler.sendMessageDelayed(peekMsg,
832                                peeking ? 0 : mNotificationPeekTapDuration);
833                    }
834
835                    // check for fling
836                    if (mVT != null) {
837                        mVT.addMovement(event);
838                        mVT.computeCurrentVelocity(1000);
839                        // require a little more oomph once we're already in peekaboo mode
840                        if (!panelShowing && (
841                               (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
842                            || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
843                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
844                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
845                            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
846                            mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
847                        }
848                    }
849                    return true;
850                case MotionEvent.ACTION_UP:
851                case MotionEvent.ACTION_CANCEL:
852                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
853                    if (peeking) {
854                        mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 250);
855                    }
856                    mVT.recycle();
857                    mVT = null;
858                    return true;
859            }
860            return false;
861        }
862    }
863
864    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
865        if (DEBUG) {
866            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
867        }
868        // Construct the icon.
869        final StatusBarIconView iconView = new StatusBarIconView(mContext,
870                notification.pkg + "/0x" + Integer.toHexString(notification.id));
871        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
872
873        final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
874                    notification.notification.icon,
875                    notification.notification.iconLevel,
876                    notification.notification.number);
877        if (!iconView.set(ic)) {
878            handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
879            return null;
880        }
881        // Construct the expanded view.
882        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
883        if (!inflateViews(entry, mPile)) {
884            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
885                    + notification);
886            return null;
887        }
888
889        // Add the icon.
890        mNotns.add(entry);
891        refreshIcons();
892
893        return iconView;
894    }
895
896    private void refreshIcons() {
897        // XXX: need to implement a new limited linear layout class
898        // to avoid removing & readding everything
899
900        final int ICON_LIMIT = 4;
901        final LinearLayout.LayoutParams params
902            = new LinearLayout.LayoutParams(mIconSize, mIconSize);
903
904        int N = mNotns.size();
905
906        if (DEBUG) {
907            Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
908        }
909
910        ArrayList<View> toShow = new ArrayList<View>();
911
912        for (int i=0; i<ICON_LIMIT; i++) {
913            if (i>=N) break;
914            toShow.add(mNotns.get(N-i-1).icon);
915        }
916
917        ArrayList<View> toRemove = new ArrayList<View>();
918        for (int i=0; i<mIconLayout.getChildCount(); i++) {
919            View child = mIconLayout.getChildAt(i);
920            if (!toShow.contains(child)) {
921                toRemove.add(child);
922            }
923        }
924
925        for (View remove : toRemove) {
926            mIconLayout.removeView(remove);
927        }
928
929        for (int i=0; i<toShow.size(); i++) {
930            View v = toShow.get(i);
931            if (v.getParent() == null) {
932                mIconLayout.addView(toShow.get(i), i, params);
933            }
934        }
935
936        loadNotificationPanel();
937        refreshNotificationTrigger();
938    }
939
940    private void loadNotificationPanel() {
941        int N = mNotns.size();
942
943        ArrayList<View> toShow = new ArrayList<View>();
944
945        for (int i=0; i<N; i++) {
946            View row = mNotns.get(N-i-1).row;
947            toShow.add(row);
948        }
949
950        ArrayList<View> toRemove = new ArrayList<View>();
951        for (int i=0; i<mPile.getChildCount(); i++) {
952            View child = mPile.getChildAt(i);
953            if (!toShow.contains(child)) {
954                toRemove.add(child);
955            }
956        }
957
958        for (View remove : toRemove) {
959            mPile.removeView(remove);
960        }
961
962        for (int i=0; i<toShow.size(); i++) {
963            View v = toShow.get(i);
964            if (v.getParent() == null) {
965                mPile.addView(toShow.get(i));
966            }
967        }
968    }
969
970    void workAroundBadLayerDrawableOpacity(View v) {
971        LayerDrawable d = (LayerDrawable)v.getBackground();
972        v.setBackgroundDrawable(null);
973        d.setOpacity(PixelFormat.TRANSLUCENT);
974        v.setBackgroundDrawable(d);
975    }
976
977    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
978        StatusBarNotification sbn = entry.notification;
979        RemoteViews remoteViews = sbn.notification.contentView;
980        if (remoteViews == null) {
981            return false;
982        }
983
984        // create the row view
985        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
986                Context.LAYOUT_INFLATER_SERVICE);
987        View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false);
988        workAroundBadLayerDrawableOpacity(row);
989        View vetoButton = row.findViewById(R.id.veto);
990        if (entry.notification.isClearable()) {
991            final String _pkg = sbn.pkg;
992            final String _tag = sbn.tag;
993            final int _id = sbn.id;
994            vetoButton.setOnClickListener(new View.OnClickListener() {
995                    public void onClick(View v) {
996                        try {
997                            mBarService.onNotificationClear(_pkg, _tag, _id);
998                        } catch (RemoteException ex) {
999                            // system process is dead if we're here.
1000                        }
1001                    }
1002                });
1003        } else {
1004            vetoButton.setVisibility(View.INVISIBLE);
1005        }
1006
1007        // the large icon
1008        ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
1009        if (sbn.notification.largeIcon != null) {
1010            largeIcon.setImageBitmap(sbn.notification.largeIcon);
1011        } else {
1012            largeIcon.getLayoutParams().width = 0;
1013            largeIcon.setVisibility(View.INVISIBLE);
1014        }
1015
1016        // bind the click event to the content area
1017        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
1018        // XXX: update to allow controls within notification views
1019        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
1020//        content.setOnFocusChangeListener(mFocusChangeListener);
1021        PendingIntent contentIntent = sbn.notification.contentIntent;
1022        if (contentIntent != null) {
1023            content.setOnClickListener(new NotificationClicker(contentIntent,
1024                        sbn.pkg, sbn.tag, sbn.id));
1025        } else {
1026            content.setOnClickListener(null);
1027        }
1028
1029        View expanded = null;
1030        Exception exception = null;
1031        try {
1032            expanded = remoteViews.apply(mContext, content);
1033        }
1034        catch (RuntimeException e) {
1035            exception = e;
1036        }
1037        if (expanded == null) {
1038            final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
1039            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
1040            return false;
1041        } else {
1042            content.addView(expanded);
1043            row.setDrawingCacheEnabled(true);
1044        }
1045
1046        entry.row = row;
1047        entry.content = content;
1048        entry.expanded = expanded;
1049
1050        return true;
1051    }
1052
1053    public class ShadowController {
1054        boolean mShowShadows;
1055        Map<View, View> mShadowsForElements = new IdentityHashMap<View, View>(7);
1056        Map<View, View> mElementsForShadows = new IdentityHashMap<View, View>(7);
1057        LayoutTransition mElementTransition, mShadowTransition;
1058
1059        View mTouchTarget;
1060
1061        ShadowController(boolean showShadows) {
1062            mShowShadows = showShadows;
1063            mTouchTarget = null;
1064
1065            mElementTransition = new LayoutTransition();
1066//            AnimatorSet s = new AnimatorSet();
1067//            s.play(ObjectAnimator.ofInt(null, "top", 48, 0))
1068//                .with(ObjectAnimator.ofFloat(null, "scaleY", 0.5f, 1f))
1069//                .with(ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f))
1070//                ;
1071            mElementTransition.setAnimator(LayoutTransition.APPEARING, //s);
1072                   ObjectAnimator.ofInt(null, "top", 48, 0));
1073            mElementTransition.setDuration(LayoutTransition.APPEARING, 100);
1074            mElementTransition.setStartDelay(LayoutTransition.APPEARING, 0);
1075
1076//            s = new AnimatorSet();
1077//            s.play(ObjectAnimator.ofInt(null, "top", 0, 48))
1078//                .with(ObjectAnimator.ofFloat(null, "scaleY", 1f, 0.5f))
1079//                .with(ObjectAnimator.ofFloat(null, "alpha", 1f, 0.5f))
1080//                ;
1081            mElementTransition.setAnimator(LayoutTransition.DISAPPEARING, //s);
1082                    ObjectAnimator.ofInt(null, "top", 0, 48));
1083            mElementTransition.setDuration(LayoutTransition.DISAPPEARING, 400);
1084
1085            mShadowTransition = new LayoutTransition();
1086            mShadowTransition.setAnimator(LayoutTransition.APPEARING,
1087                    ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
1088            mShadowTransition.setDuration(LayoutTransition.APPEARING, 200);
1089            mShadowTransition.setStartDelay(LayoutTransition.APPEARING, 100);
1090            mShadowTransition.setAnimator(LayoutTransition.DISAPPEARING,
1091                    ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
1092            mShadowTransition.setDuration(LayoutTransition.DISAPPEARING, 100);
1093
1094            ViewGroup bar = (ViewGroup) TabletStatusBar.this.mBarContents;
1095            bar.setLayoutTransition(mElementTransition);
1096            ViewGroup nav = (ViewGroup) TabletStatusBar.this.mNavigationArea;
1097            nav.setLayoutTransition(mElementTransition);
1098            ViewGroup shadowGroup = (ViewGroup) bar.findViewById(R.id.shadows);
1099            shadowGroup.setLayoutTransition(mShadowTransition);
1100        }
1101
1102        public void add(View element, View shadow) {
1103            shadow.setOnTouchListener(makeTouchListener());
1104            mShadowsForElements.put(element, shadow);
1105            mElementsForShadows.put(shadow, element);
1106        }
1107
1108        public boolean getShadowState() {
1109            return mShowShadows;
1110        }
1111
1112        public View.OnTouchListener makeTouchListener() {
1113            return new View.OnTouchListener() {
1114                public boolean onTouch(View v, MotionEvent ev) {
1115                    final int action = ev.getAction();
1116
1117                    if (DEBUG) Slog.d(TAG, "ShadowController: v=" + v + ", ev=" + ev);
1118
1119                    // currently redirecting events?
1120                    if (mTouchTarget == null) {
1121                        mTouchTarget = mElementsForShadows.get(v);
1122                    }
1123
1124                    if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) {
1125                        boolean last = false;
1126                        switch (action) {
1127                            case MotionEvent.ACTION_CANCEL:
1128                            case MotionEvent.ACTION_UP:
1129                                mHandler.removeMessages(MSG_RESTORE_SHADOWS);
1130                                if (mShowShadows) {
1131                                    mHandler.sendEmptyMessageDelayed(MSG_RESTORE_SHADOWS,
1132                                            v == mNotificationShadow ? 5000 : 500);
1133                                }
1134                                last = true;
1135                                break;
1136                            case MotionEvent.ACTION_DOWN:
1137                                mHandler.removeMessages(MSG_RESTORE_SHADOWS);
1138                                setElementShadow(mTouchTarget, false);
1139                                break;
1140                        }
1141                        mTouchTarget.dispatchTouchEvent(ev);
1142                        if (last) mTouchTarget = null;
1143                        return true;
1144                    }
1145
1146                    return false;
1147                }
1148            };
1149        }
1150
1151        public void refresh() {
1152            for (View element : mShadowsForElements.keySet()) {
1153                setElementShadow(element, mShowShadows);
1154            }
1155        }
1156
1157        public void showAllShadows() {
1158            mShowShadows = true;
1159            refresh();
1160        }
1161
1162        public void hideAllShadows() {
1163            mShowShadows = false;
1164            refresh();
1165        }
1166
1167        // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are
1168        // disabled (and should not be shadowed or re-shown)
1169        public void setElementShadow(View button, boolean shade) {
1170            View shadow = mShadowsForElements.get(button);
1171            if (shadow != null) {
1172                if (button.getVisibility() != View.GONE) {
1173                    shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE);
1174                    button.setVisibility(shade ? View.INVISIBLE : View.VISIBLE);
1175                }
1176            }
1177        }
1178
1179        // Hide both element and shadow, using default layout animations.
1180        public void hideElement(View button) {
1181            Slog.d(TAG, "hiding: " + button);
1182            View shadow = mShadowsForElements.get(button);
1183            if (shadow != null) {
1184                shadow.setVisibility(View.GONE);
1185            }
1186            button.setVisibility(View.GONE);
1187        }
1188
1189        // Honoring the current shadow state.
1190        public void showElement(View button) {
1191            Slog.d(TAG, "showing: " + button);
1192            View shadow = mShadowsForElements.get(button);
1193            if (shadow != null) {
1194                shadow.setVisibility(mShowShadows ? View.VISIBLE : View.INVISIBLE);
1195            }
1196            button.setVisibility(mShowShadows ? View.INVISIBLE : View.VISIBLE);
1197        }
1198    }
1199
1200    public class TouchOutsideListener implements View.OnTouchListener {
1201        private int mMsg;
1202        private StatusBarPanel mPanel;
1203
1204        public TouchOutsideListener(int msg, StatusBarPanel panel) {
1205            mMsg = msg;
1206            mPanel = panel;
1207        }
1208
1209        public boolean onTouch(View v, MotionEvent ev) {
1210            final int action = ev.getAction();
1211            if (action == MotionEvent.ACTION_OUTSIDE
1212                    || (action == MotionEvent.ACTION_DOWN
1213                        && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
1214                mHandler.removeMessages(mMsg);
1215                mHandler.sendEmptyMessage(mMsg);
1216                return true;
1217            }
1218            return false;
1219        }
1220    }
1221
1222    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1223        pw.print("mDisabled=0x");
1224        pw.println(Integer.toHexString(mDisabled));
1225    }
1226}
1227
1228
1229