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