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