PhoneStatusBar.java revision edbdd3a024ca35c331036823dde1f484d3333b31
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.phone;
18
19import android.app.Service;
20import android.app.ActivityManagerNative;
21import android.app.Dialog;
22import android.app.Notification;
23import android.app.PendingIntent;
24import android.app.Service;
25import android.app.StatusBarManager;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.res.Resources;
31import android.content.res.Configuration;
32import android.graphics.PixelFormat;
33import android.graphics.Rect;
34import android.graphics.drawable.Drawable;
35import android.net.Uri;
36import android.os.Bundle;
37import android.os.IBinder;
38import android.os.RemoteException;
39import android.os.Handler;
40import android.os.Message;
41import android.os.ServiceManager;
42import android.os.SystemClock;
43import android.text.TextUtils;
44import android.util.Slog;
45import android.util.Log;
46import android.view.Display;
47import android.view.Gravity;
48import android.view.IWindowManager;
49import android.view.KeyEvent;
50import android.view.LayoutInflater;
51import android.view.MotionEvent;
52import android.view.Surface;
53import android.view.VelocityTracker;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.Window;
57import android.view.WindowManager;
58import android.view.WindowManagerImpl;
59import android.view.animation.Animation;
60import android.view.animation.AnimationUtils;
61import android.widget.ImageView;
62import android.widget.LinearLayout;
63import android.widget.RemoteViews;
64import android.widget.ScrollView;
65import android.widget.TextView;
66import android.widget.FrameLayout;
67
68import java.io.FileDescriptor;
69import java.io.PrintWriter;
70import java.util.ArrayList;
71import java.util.HashMap;
72import java.util.Set;
73
74import com.android.internal.statusbar.StatusBarIcon;
75import com.android.internal.statusbar.StatusBarIconList;
76import com.android.internal.statusbar.StatusBarNotification;
77
78import com.android.systemui.R;
79import com.android.systemui.recent.RecentsPanelView;
80import com.android.systemui.statusbar.NotificationData;
81import com.android.systemui.statusbar.StatusBar;
82import com.android.systemui.statusbar.StatusBarIconView;
83import com.android.systemui.statusbar.policy.DateView;
84
85
86public class PhoneStatusBar extends StatusBar {
87    static final String TAG = "PhoneStatusBar";
88    static final boolean SPEW = false;
89    public static final boolean DEBUG = false;
90
91    public static final String ACTION_STATUSBAR_START
92            = "com.android.internal.policy.statusbar.START";
93
94    static final int EXPANDED_LEAVE_ALONE = -10000;
95    static final int EXPANDED_FULL_OPEN = -10001;
96
97    private static final int MSG_ANIMATE = 1000;
98    private static final int MSG_ANIMATE_REVEAL = 1001;
99    private static final int MSG_SHOW_INTRUDER = 1002;
100    private static final int MSG_HIDE_INTRUDER = 1003;
101    private static final int MSG_OPEN_RECENTS_PANEL = 1020;
102    private static final int MSG_CLOSE_RECENTS_PANEL = 1021;
103
104    // will likely move to a resource or other tunable param at some point
105    private static final int INTRUDER_ALERT_DECAY_MS = 10000;
106
107    PhoneStatusBarPolicy mIconPolicy;
108
109    int mIconSize;
110    Display mDisplay;
111
112    IWindowManager mWindowManager;
113
114    PhoneStatusBarView mStatusBarView;
115    int mPixelFormat;
116    H mHandler = new H();
117    Object mQueueLock = new Object();
118
119    // icons
120    LinearLayout mIcons;
121    IconMerger mNotificationIcons;
122    LinearLayout mStatusIcons;
123
124    // expanded notifications
125    Dialog mExpandedDialog;
126    ExpandedView mExpandedView;
127    WindowManager.LayoutParams mExpandedParams;
128    ScrollView mScrollView;
129    View mNotificationLinearLayout;
130    View mExpandedContents;
131    // top bar
132    TextView mNoNotificationsTitle;
133    TextView mClearButton;
134    // drag bar
135    CloseDragHandle mCloseView;
136    // ongoing
137    NotificationData mOngoing = new NotificationData();
138    TextView mOngoingTitle;
139    ViewGroup mOngoingItems;
140    // latest
141    NotificationData mLatest = new NotificationData();
142    TextView mLatestTitle;
143    ViewGroup mLatestItems;
144    // position
145    int[] mPositionTmp = new int[2];
146    boolean mExpanded;
147    boolean mExpandedVisible;
148
149    // the date view
150    DateView mDateView;
151
152    // for immersive activities
153    private View mIntruderAlertView;
154
155    // on-screen navigation buttons
156    private NavigationBarView mNavigationBarView = null;
157
158    // the tracker view
159    TrackingView mTrackingView;
160    WindowManager.LayoutParams mTrackingParams;
161    int mTrackingPosition; // the position of the top of the tracking view.
162    private boolean mPanelSlightlyVisible;
163
164    // ticker
165    private Ticker mTicker;
166    private View mTickerView;
167    private boolean mTicking;
168
169    // Recent applications
170    private RecentsPanelView mRecentsPanel;
171
172    // Tracking finger for opening/closing.
173    int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore
174    boolean mTracking;
175    VelocityTracker mVelocityTracker;
176
177    static final int ANIM_FRAME_DURATION = (1000/60);
178
179    boolean mAnimating;
180    long mCurAnimationTime;
181    float mDisplayHeight;
182    float mAnimY;
183    float mAnimVel;
184    float mAnimAccel;
185    long mAnimLastTime;
186    boolean mAnimatingReveal = false;
187    int mViewDelta;
188    int[] mAbsPos = new int[2];
189
190    // for disabling the status bar
191    int mDisabled = 0;
192
193    private class ExpandedDialog extends Dialog {
194        ExpandedDialog(Context context) {
195            super(context, com.android.internal.R.style.Theme_Light_NoTitleBar);
196        }
197
198        @Override
199        public boolean dispatchKeyEvent(KeyEvent event) {
200            boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
201            switch (event.getKeyCode()) {
202            case KeyEvent.KEYCODE_BACK:
203                if (!down) {
204                    animateCollapse();
205                }
206                return true;
207            }
208            return super.dispatchKeyEvent(event);
209        }
210    }
211
212    @Override
213    public void start() {
214        mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
215                .getDefaultDisplay();
216
217        mWindowManager = IWindowManager.Stub.asInterface(
218                ServiceManager.getService(Context.WINDOW_SERVICE));
219
220        super.start();
221
222        addNavigationBar();
223
224        //addIntruderView();
225
226        // Lastly, call to the icon policy to install/update all the icons.
227        mIconPolicy = new PhoneStatusBarPolicy(mContext);
228    }
229
230    // ================================================================================
231    // Constructing the view
232    // ================================================================================
233    protected View makeStatusBarView() {
234        final Context context = mContext;
235
236        Resources res = context.getResources();
237
238        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
239
240        ExpandedView expanded = (ExpandedView)View.inflate(context,
241                R.layout.status_bar_expanded, null);
242        expanded.mService = this;
243
244        mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null);
245        mIntruderAlertView.setVisibility(View.GONE);
246        mIntruderAlertView.setClickable(true);
247
248        try {
249            boolean showNav = res.getBoolean(R.bool.config_showNavigationBar);
250            if (showNav) {
251                mNavigationBarView =
252                    (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);
253            }
254        } catch (Resources.NotFoundException ex) {
255            // no nav bar for you
256        }
257
258        PhoneStatusBarView sb = (PhoneStatusBarView)View.inflate(context,
259                R.layout.status_bar, null);
260        sb.mService = this;
261
262        // figure out which pixel-format to use for the status bar.
263        mPixelFormat = PixelFormat.TRANSLUCENT;
264        Drawable bg = sb.getBackground();
265        if (bg != null) {
266            mPixelFormat = bg.getOpacity();
267        }
268
269        mStatusBarView = sb;
270        mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons);
271        mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons);
272        mIcons = (LinearLayout)sb.findViewById(R.id.icons);
273        mTickerView = sb.findViewById(R.id.ticker);
274        mDateView = (DateView)sb.findViewById(R.id.date);
275
276        mExpandedDialog = new ExpandedDialog(context);
277        mExpandedView = expanded;
278        mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout);
279        mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle);
280        mOngoingItems = (ViewGroup)expanded.findViewById(R.id.ongoingItems);
281        mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle);
282        mLatestItems = (ViewGroup)expanded.findViewById(R.id.latestItems);
283        mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle);
284        mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button);
285        mClearButton.setOnClickListener(mClearButtonListener);
286        mScrollView = (ScrollView)expanded.findViewById(R.id.scroll);
287        mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout);
288
289        mOngoingTitle.setVisibility(View.GONE);
290        mLatestTitle.setVisibility(View.GONE);
291
292        mTicker = new MyTicker(context, sb);
293
294        TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText);
295        tickerView.mTicker = mTicker;
296
297        mTrackingView = (TrackingView)View.inflate(context, R.layout.status_bar_tracking, null);
298        mTrackingView.mService = this;
299        mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close);
300        mCloseView.mService = this;
301
302        mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
303
304        // set the inital view visibility
305        setAreThereNotifications();
306        mDateView.setVisibility(View.INVISIBLE);
307
308        // Recents Panel
309        initializeRecentsPanel();
310
311        // receive broadcasts
312        IntentFilter filter = new IntentFilter();
313        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
314        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
315        filter.addAction(Intent.ACTION_SCREEN_OFF);
316        context.registerReceiver(mBroadcastReceiver, filter);
317
318        return sb;
319    }
320
321    protected WindowManager.LayoutParams getRecentsLayoutParams() {
322        boolean translucent = false;
323        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
324                ViewGroup.LayoutParams.WRAP_CONTENT,
325                ViewGroup.LayoutParams.WRAP_CONTENT,
326                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
327                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
328                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
329                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
330                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
331                (translucent ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT));
332        lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
333        lp.setTitle("RecentsPanel");
334        lp.windowAnimations = R.style.Animation_RecentPanel;
335        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
336        | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
337        return lp;
338    }
339
340    protected void initializeRecentsPanel() {
341        // Recents Panel
342        boolean visible = false;
343        if (mRecentsPanel != null) {
344            visible = mRecentsPanel.getVisibility() == View.VISIBLE;
345            WindowManagerImpl.getDefault().removeView(mRecentsPanel);
346        }
347        mRecentsPanel = (RecentsPanelView) View.inflate(mContext,
348                R.layout.status_bar_recent_panel, null);
349
350        mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
351                mRecentsPanel));
352        mRecentsPanel.setVisibility(View.GONE);
353        WindowManager.LayoutParams lp = getRecentsLayoutParams();
354
355        WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
356        mRecentsPanel.setBar(this);
357        if (visible) {
358            // need to set visibility to View.GONE earlier since that
359            // triggers refreshing application list
360            mRecentsPanel.setVisibility(View.VISIBLE);
361            mRecentsPanel.show(true, false);
362        }
363
364    }
365
366    protected int getStatusBarGravity() {
367        return Gravity.TOP | Gravity.FILL_HORIZONTAL;
368    }
369
370    public int getStatusBarHeight() {
371        final Resources res = mContext.getResources();
372        return res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
373    }
374
375    // For small-screen devices (read: phones) that lack hardware navigation buttons
376    private void addNavigationBar() {
377        if (mNavigationBarView == null) return;
378
379        mNavigationBarView.reorient();
380        WindowManagerImpl.getDefault().addView(
381                mNavigationBarView, getNavigationBarLayoutParams());
382    }
383
384    private void repositionNavigationBar() {
385        if (mNavigationBarView == null) return;
386
387        mNavigationBarView.reorient();
388        WindowManagerImpl.getDefault().updateViewLayout(
389                mNavigationBarView, getNavigationBarLayoutParams());
390    }
391
392    private WindowManager.LayoutParams getNavigationBarLayoutParams() {
393        final int rotation = mDisplay.getRotation();
394        final boolean sideways =
395            (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
396
397        final Resources res = mContext.getResources();
398        final int size = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
399
400        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
401                sideways ? size : ViewGroup.LayoutParams.MATCH_PARENT,
402                sideways ? ViewGroup.LayoutParams.MATCH_PARENT : size,
403                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
404                    0
405                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
406                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
407                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
408                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
409                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
410                    | WindowManager.LayoutParams.FLAG_SLIPPERY,
411                PixelFormat.TRANSLUCENT);
412
413        lp.setTitle("NavigationBar");
414        switch (rotation) {
415            case Surface.ROTATION_90:
416                // device has been turned 90deg counter-clockwise
417                lp.gravity = Gravity.RIGHT | Gravity.FILL_VERTICAL;
418                break;
419            case Surface.ROTATION_270:
420                // device has been turned 90deg clockwise
421                lp.gravity = (NavigationBarView.NAVBAR_ALWAYS_AT_RIGHT ? Gravity.RIGHT
422                                                                       : Gravity.LEFT)
423                             | Gravity.FILL_VERTICAL;
424                break;
425            default:
426                lp.gravity = Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
427                break;
428        }
429        lp.windowAnimations = 0;
430
431        return lp;
432    }
433
434    private void addIntruderView() {
435        final int height = getStatusBarHeight();
436
437        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
438                ViewGroup.LayoutParams.MATCH_PARENT,
439                ViewGroup.LayoutParams.WRAP_CONTENT,
440                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
441                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
442                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
443                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
444                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
445                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
446                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
447                PixelFormat.TRANSLUCENT);
448        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
449        lp.y += height * 1.5; // FIXME
450        lp.setTitle("IntruderAlert");
451        lp.windowAnimations = com.android.internal.R.style.Animation_StatusBar_IntruderAlert;
452
453        WindowManagerImpl.getDefault().addView(mIntruderAlertView, lp);
454    }
455
456    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
457        if (SPEW) Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
458                + " icon=" + icon);
459        StatusBarIconView view = new StatusBarIconView(mContext, slot);
460        view.set(icon);
461        mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize));
462    }
463
464    public void updateIcon(String slot, int index, int viewIndex,
465            StatusBarIcon old, StatusBarIcon icon) {
466        if (SPEW) Slog.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
467                + " old=" + old + " icon=" + icon);
468        StatusBarIconView view = (StatusBarIconView)mStatusIcons.getChildAt(viewIndex);
469        view.set(icon);
470    }
471
472    public void removeIcon(String slot, int index, int viewIndex) {
473        if (SPEW) Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex);
474        mStatusIcons.removeViewAt(viewIndex);
475    }
476
477    public void addNotification(IBinder key, StatusBarNotification notification) {
478        StatusBarIconView iconView = addNotificationViews(key, notification);
479        if (iconView == null) return;
480
481        boolean immersive = false;
482        try {
483            immersive = ActivityManagerNative.getDefault().isTopActivityImmersive();
484            Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
485        } catch (RemoteException ex) {
486        }
487        if (immersive) {
488            if ((notification.notification.flags & Notification.FLAG_HIGH_PRIORITY) != 0) {
489                Slog.d(TAG, "Presenting high-priority notification in immersive activity");
490                // special new transient ticker mode
491                // 1. Populate mIntruderAlertView
492
493                ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon);
494                TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText);
495                alertIcon.setImageDrawable(StatusBarIconView.getIcon(
496                    alertIcon.getContext(),
497                    iconView.getStatusBarIcon()));
498                alertText.setText(notification.notification.tickerText);
499
500                View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content);
501                button.setOnClickListener(
502                    new Launcher(notification.notification.contentIntent,
503                        notification.pkg, notification.tag, notification.id));
504
505                // 2. Animate mIntruderAlertView in
506                mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER);
507
508                // 3. Set alarm to age the notification off (TODO)
509                mHandler.removeMessages(MSG_HIDE_INTRUDER);
510                mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
511            }
512        } else if (notification.notification.fullScreenIntent != null) {
513            // not immersive & a full-screen alert should be shown
514            Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;"
515                    + " sending fullScreenIntent");
516            try {
517                notification.notification.fullScreenIntent.send();
518            } catch (PendingIntent.CanceledException e) {
519            }
520        } else {
521            // usual case: status bar visible & not immersive
522
523            // show the ticker
524            tick(notification);
525        }
526
527        // Recalculate the position of the sliding windows and the titles.
528        setAreThereNotifications();
529        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
530    }
531
532    public void updateNotification(IBinder key, StatusBarNotification notification) {
533        Slog.d(TAG, "updateNotification key=" + key + " notification=" + notification);
534
535        NotificationData oldList;
536        NotificationData.Entry oldEntry = mOngoing.findByKey(key);
537        if (oldEntry != null) {
538            oldList = mOngoing;
539        } else {
540            oldEntry = mLatest.findByKey(key);
541            if (oldEntry == null) {
542                Slog.w(TAG, "updateNotification for unknown key: " + key);
543                return;
544            }
545            oldList = mLatest;
546        }
547        final StatusBarNotification oldNotification = oldEntry.notification;
548        final RemoteViews oldContentView = oldNotification.notification.contentView;
549
550        final RemoteViews contentView = notification.notification.contentView;
551
552        if (false) {
553            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
554                    + " ongoing=" + oldNotification.isOngoing()
555                    + " expanded=" + oldEntry.expanded
556                    + " contentView=" + oldContentView);
557            Slog.d(TAG, "new notification: when=" + notification.notification.when
558                    + " ongoing=" + oldNotification.isOngoing()
559                    + " contentView=" + contentView);
560        }
561
562        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
563        // didn't change.
564        if (notification.notification.when == oldNotification.notification.when
565                && notification.isOngoing() == oldNotification.isOngoing()
566                && oldEntry.expanded != null
567                && contentView != null && oldContentView != null
568                && contentView.getPackage() != null
569                && oldContentView.getPackage() != null
570                && oldContentView.getPackage().equals(contentView.getPackage())
571                && oldContentView.getLayoutId() == contentView.getLayoutId()) {
572            if (SPEW) Slog.d(TAG, "reusing notification");
573            oldEntry.notification = notification;
574            try {
575                // Reapply the RemoteViews
576                contentView.reapply(mContext, oldEntry.content);
577                // update the contentIntent
578                final PendingIntent contentIntent = notification.notification.contentIntent;
579                if (contentIntent != null) {
580                    oldEntry.content.setOnClickListener(new Launcher(contentIntent,
581                                notification.pkg, notification.tag, notification.id));
582                } else {
583                    oldEntry.content.setOnClickListener(null);
584                }
585                // Update the icon.
586                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
587                        notification.notification.icon, notification.notification.iconLevel,
588                        notification.notification.number);
589                if (!oldEntry.icon.set(ic)) {
590                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
591                    return;
592                }
593            }
594            catch (RuntimeException e) {
595                // It failed to add cleanly.  Log, and remove the view from the panel.
596                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
597                removeNotificationViews(key);
598                addNotificationViews(key, notification);
599            }
600        } else {
601            if (SPEW) Slog.d(TAG, "not reusing notification");
602            removeNotificationViews(key);
603            addNotificationViews(key, notification);
604        }
605
606        // Restart the ticker if it's still running
607        if (notification.notification.tickerText != null
608                && !TextUtils.equals(notification.notification.tickerText,
609                    oldEntry.notification.notification.tickerText)) {
610            tick(notification);
611        }
612
613        // Recalculate the position of the sliding windows and the titles.
614        setAreThereNotifications();
615        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
616    }
617
618    public void removeNotification(IBinder key) {
619        if (SPEW) Slog.d(TAG, "removeNotification key=" + key);
620        StatusBarNotification old = removeNotificationViews(key);
621
622        if (old != null) {
623            // Cancel the ticker if it's still running
624            mTicker.removeEntry(old);
625
626            // Recalculate the position of the sliding windows and the titles.
627            setAreThereNotifications();
628            updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
629        }
630    }
631
632    private int chooseIconIndex(boolean isOngoing, int viewIndex) {
633        final int latestSize = mLatest.size();
634        if (isOngoing) {
635            return latestSize + (mOngoing.size() - viewIndex);
636        } else {
637            return latestSize - viewIndex;
638        }
639    }
640
641    @Override
642    protected void onConfigurationChanged(Configuration newConfig) {
643        initializeRecentsPanel();
644    }
645
646
647    View[] makeNotificationView(StatusBarNotification notification, ViewGroup parent) {
648        Notification n = notification.notification;
649        RemoteViews remoteViews = n.contentView;
650        if (remoteViews == null) {
651            return null;
652        }
653
654        // create the row view
655        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
656                Context.LAYOUT_INFLATER_SERVICE);
657        View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
658
659        // wire up the veto button
660        View vetoButton = row.findViewById(R.id.veto);
661        if (notification.isClearable()) {
662            final String _pkg = notification.pkg;
663            final String _tag = notification.tag;
664            final int _id = notification.id;
665            vetoButton.setOnClickListener(new View.OnClickListener() {
666                    public void onClick(View v) {
667                        try {
668                            mBarService.onNotificationClear(_pkg, _tag, _id);
669                        } catch (RemoteException ex) {
670                            // system process is dead if we're here.
671                        }
672                    }
673                });
674        } else {
675            if ((notification.notification.flags & Notification.FLAG_ONGOING_EVENT) == 0) {
676                vetoButton.setVisibility(View.INVISIBLE);
677            } else {
678                vetoButton.setVisibility(View.GONE);
679            }
680        }
681
682        // the large icon
683        ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
684        if (notification.notification.largeIcon != null) {
685            largeIcon.setImageBitmap(notification.notification.largeIcon);
686        } else {
687            largeIcon.getLayoutParams().width = 0;
688            largeIcon.setVisibility(View.INVISIBLE);
689        }
690
691        // bind the click event to the content area
692        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
693        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
694        content.setOnFocusChangeListener(mFocusChangeListener);
695        PendingIntent contentIntent = n.contentIntent;
696        if (contentIntent != null) {
697            content.setOnClickListener(new Launcher(contentIntent, notification.pkg,
698                        notification.tag, notification.id));
699        } else {
700            content.setOnClickListener(null);
701        }
702
703        View expanded = null;
704        Exception exception = null;
705        try {
706            expanded = remoteViews.apply(mContext, content);
707        }
708        catch (RuntimeException e) {
709            exception = e;
710        }
711        if (expanded == null) {
712            String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id);
713            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
714            return null;
715        } else {
716            content.addView(expanded);
717            row.setDrawingCacheEnabled(true);
718        }
719
720        return new View[] { row, content, expanded };
721    }
722
723    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
724        NotificationData list;
725        ViewGroup parent;
726        final boolean isOngoing = notification.isOngoing();
727        if (isOngoing) {
728            list = mOngoing;
729            parent = mOngoingItems;
730        } else {
731            list = mLatest;
732            parent = mLatestItems;
733        }
734        // Construct the expanded view.
735        final View[] views = makeNotificationView(notification, parent);
736        if (views == null) {
737            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
738                    + notification);
739            return null;
740        }
741        final View row = views[0];
742        final View content = views[1];
743        final View expanded = views[2];
744        // Construct the icon.
745        final StatusBarIconView iconView = new StatusBarIconView(mContext,
746                notification.pkg + "/0x" + Integer.toHexString(notification.id));
747        final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon,
748                    notification.notification.iconLevel, notification.notification.number);
749        if (!iconView.set(ic)) {
750            handleNotificationError(key, notification, "Coulding create icon: " + ic);
751            return null;
752        }
753        // Add the expanded view.
754        final int viewIndex = list.add(key, notification, row, content, expanded, iconView);
755        parent.addView(row, viewIndex);
756        // Add the icon.
757        final int iconIndex = chooseIconIndex(isOngoing, viewIndex);
758        mNotificationIcons.addView(iconView, iconIndex);
759        return iconView;
760    }
761
762    StatusBarNotification removeNotificationViews(IBinder key) {
763        NotificationData.Entry entry = mOngoing.remove(key);
764        if (entry == null) {
765            entry = mLatest.remove(key);
766            if (entry == null) {
767                Slog.w(TAG, "removeNotification for unknown key: " + key);
768                return null;
769            }
770        }
771        // Remove the expanded view.
772        ((ViewGroup)entry.row.getParent()).removeView(entry.row);
773        // Remove the icon.
774        ((ViewGroup)entry.icon.getParent()).removeView(entry.icon);
775
776        return entry.notification;
777    }
778
779    private void setAreThereNotifications() {
780        boolean ongoing = mOngoing.hasVisibleItems();
781        boolean latest = mLatest.hasVisibleItems();
782
783        // (no ongoing notifications are clearable)
784        if (mLatest.hasClearableItems()) {
785            mClearButton.setVisibility(View.VISIBLE);
786        } else {
787            mClearButton.setVisibility(View.INVISIBLE);
788        }
789
790        mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE);
791        mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE);
792
793        if (ongoing || latest) {
794            mNoNotificationsTitle.setVisibility(View.GONE);
795        } else {
796            mNoNotificationsTitle.setVisibility(View.VISIBLE);
797        }
798    }
799
800
801    /**
802     * State is one or more of the DISABLE constants from StatusBarManager.
803     */
804    public void disable(int state) {
805        final int old = mDisabled;
806        final int diff = state ^ old;
807        mDisabled = state;
808
809        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
810            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
811                Slog.d(TAG, "DISABLE_EXPAND: yes");
812                animateCollapse();
813            }
814        }
815        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
816            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
817                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
818                if (mTicking) {
819                    mTicker.halt();
820                } else {
821                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
822                }
823            } else {
824                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
825                if (!mExpandedVisible) {
826                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
827                }
828            }
829        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
830            if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
831                Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: yes");
832                mTicker.halt();
833            }
834        }
835    }
836
837    /**
838     * All changes to the status bar and notifications funnel through here and are batched.
839     */
840    private class H extends Handler {
841        public void handleMessage(Message m) {
842            switch (m.what) {
843                case MSG_ANIMATE:
844                    doAnimation();
845                    break;
846                case MSG_ANIMATE_REVEAL:
847                    doRevealAnimation();
848                    break;
849                case MSG_SHOW_INTRUDER:
850                    setIntruderAlertVisibility(true);
851                    break;
852                case MSG_HIDE_INTRUDER:
853                    setIntruderAlertVisibility(false);
854                    break;
855                case MSG_OPEN_RECENTS_PANEL:
856                    if (DEBUG) Slog.d(TAG, "opening recents panel");
857                    if (mRecentsPanel != null) {
858                        disable(StatusBarManager.DISABLE_BACK);
859                        mRecentsPanel.setVisibility(View.VISIBLE);
860                        mRecentsPanel.show(true, true);
861                    }
862                    break;
863                case MSG_CLOSE_RECENTS_PANEL:
864                    if (DEBUG) Slog.d(TAG, "closing recents panel");
865                    if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
866                        disable(StatusBarManager.DISABLE_NONE);
867                        mRecentsPanel.show(false, true);
868                    }
869                    break;
870            }
871        }
872    }
873
874    View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() {
875        public void onFocusChange(View v, boolean hasFocus) {
876            // Because 'v' is a ViewGroup, all its children will be (un)selected
877            // too, which allows marqueeing to work.
878            v.setSelected(hasFocus);
879        }
880    };
881
882    private void makeExpandedVisible() {
883        if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
884        if (mExpandedVisible) {
885            return;
886        }
887        mExpandedVisible = true;
888        visibilityChanged(true);
889
890        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
891        mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
892        mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
893        mExpandedDialog.getWindow().setAttributes(mExpandedParams);
894        mExpandedView.requestFocus(View.FOCUS_FORWARD);
895        mTrackingView.setVisibility(View.VISIBLE);
896
897        if (!mTicking) {
898            setDateViewVisibility(true, com.android.internal.R.anim.fade_in);
899        }
900    }
901
902    public void animateExpand() {
903        if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded);
904        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
905            return ;
906        }
907        if (mExpanded) {
908            return;
909        }
910
911        prepareTracking(0, true);
912        performFling(0, 2000.0f, true);
913    }
914
915    public void animateCollapse() {
916        animateCollapse(false);
917    }
918
919    public void animateCollapse(boolean excludeRecents) {
920        if (SPEW) {
921            Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded
922                    + " mExpandedVisible=" + mExpandedVisible
923                    + " mExpanded=" + mExpanded
924                    + " mAnimating=" + mAnimating
925                    + " mAnimY=" + mAnimY
926                    + " mAnimVel=" + mAnimVel);
927        }
928
929        if (!excludeRecents) {
930            mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
931            mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
932        }
933
934        if (!mExpandedVisible) {
935            return;
936        }
937
938        int y;
939        if (mAnimating) {
940            y = (int)mAnimY;
941        } else {
942            y = mDisplay.getHeight()-1;
943        }
944        // Let the fling think that we're open so it goes in the right direction
945        // and doesn't try to re-open the windowshade.
946        mExpanded = true;
947        prepareTracking(y, false);
948        performFling(y, -2000.0f, true);
949    }
950
951    void performExpand() {
952        if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded);
953        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
954            return ;
955        }
956        if (mExpanded) {
957            return;
958        }
959
960        mExpanded = true;
961        makeExpandedVisible();
962        updateExpandedViewPos(EXPANDED_FULL_OPEN);
963
964        if (false) postStartTracing();
965    }
966
967    void performCollapse() {
968        if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded
969                + " mExpandedVisible=" + mExpandedVisible);
970
971        if (!mExpandedVisible) {
972            return;
973        }
974        mExpandedVisible = false;
975        visibilityChanged(false);
976        mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
977        mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
978        mExpandedDialog.getWindow().setAttributes(mExpandedParams);
979        mTrackingView.setVisibility(View.GONE);
980
981        if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) {
982            setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
983        }
984        setDateViewVisibility(false, com.android.internal.R.anim.fade_out);
985
986        if (!mExpanded) {
987            return;
988        }
989        mExpanded = false;
990    }
991
992    void doAnimation() {
993        if (mAnimating) {
994            if (SPEW) Slog.d(TAG, "doAnimation");
995            if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY);
996            incrementAnim();
997            if (SPEW) Slog.d(TAG, "doAnimation after  mAnimY=" + mAnimY);
998            if (mAnimY >= mDisplay.getHeight()-1) {
999                if (SPEW) Slog.d(TAG, "Animation completed to expanded state.");
1000                mAnimating = false;
1001                updateExpandedViewPos(EXPANDED_FULL_OPEN);
1002                performExpand();
1003            }
1004            else if (mAnimY < mStatusBarView.getHeight()) {
1005                if (SPEW) Slog.d(TAG, "Animation completed to collapsed state.");
1006                mAnimating = false;
1007                updateExpandedViewPos(0);
1008                performCollapse();
1009            }
1010            else {
1011                updateExpandedViewPos((int)mAnimY);
1012                mCurAnimationTime += ANIM_FRAME_DURATION;
1013                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime);
1014            }
1015        }
1016    }
1017
1018    void stopTracking() {
1019        mTracking = false;
1020        mVelocityTracker.recycle();
1021        mVelocityTracker = null;
1022    }
1023
1024    void incrementAnim() {
1025        long now = SystemClock.uptimeMillis();
1026        float t = ((float)(now - mAnimLastTime)) / 1000;            // ms -> s
1027        final float y = mAnimY;
1028        final float v = mAnimVel;                                   // px/s
1029        final float a = mAnimAccel;                                 // px/s/s
1030        mAnimY = y + (v*t) + (0.5f*a*t*t);                          // px
1031        mAnimVel = v + (a*t);                                       // px/s
1032        mAnimLastTime = now;                                        // ms
1033        //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY
1034        //        + " mAnimAccel=" + mAnimAccel);
1035    }
1036
1037    void doRevealAnimation() {
1038        final int h = mCloseView.getHeight() + mStatusBarView.getHeight();
1039        if (mAnimatingReveal && mAnimating && mAnimY < h) {
1040            incrementAnim();
1041            if (mAnimY >= h) {
1042                mAnimY = h;
1043                updateExpandedViewPos((int)mAnimY);
1044            } else {
1045                updateExpandedViewPos((int)mAnimY);
1046                mCurAnimationTime += ANIM_FRAME_DURATION;
1047                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL),
1048                        mCurAnimationTime);
1049            }
1050        }
1051    }
1052
1053    void prepareTracking(int y, boolean opening) {
1054        mTracking = true;
1055        mVelocityTracker = VelocityTracker.obtain();
1056        if (opening) {
1057            mAnimAccel = 2000.0f;
1058            mAnimVel = 200;
1059            mAnimY = mStatusBarView.getHeight();
1060            updateExpandedViewPos((int)mAnimY);
1061            mAnimating = true;
1062            mAnimatingReveal = true;
1063            mHandler.removeMessages(MSG_ANIMATE);
1064            mHandler.removeMessages(MSG_ANIMATE_REVEAL);
1065            long now = SystemClock.uptimeMillis();
1066            mAnimLastTime = now;
1067            mCurAnimationTime = now + ANIM_FRAME_DURATION;
1068            mAnimating = true;
1069            mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL),
1070                    mCurAnimationTime);
1071            makeExpandedVisible();
1072        } else {
1073            // it's open, close it?
1074            if (mAnimating) {
1075                mAnimating = false;
1076                mHandler.removeMessages(MSG_ANIMATE);
1077            }
1078            updateExpandedViewPos(y + mViewDelta);
1079        }
1080    }
1081
1082    void performFling(int y, float vel, boolean always) {
1083        mAnimatingReveal = false;
1084        mDisplayHeight = mDisplay.getHeight();
1085
1086        mAnimY = y;
1087        mAnimVel = vel;
1088
1089        //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel);
1090
1091        if (mExpanded) {
1092            if (!always && (
1093                    vel > 200.0f
1094                    || (y > (mDisplayHeight-25) && vel > -200.0f))) {
1095                // We are expanded, but they didn't move sufficiently to cause
1096                // us to retract.  Animate back to the expanded position.
1097                mAnimAccel = 2000.0f;
1098                if (vel < 0) {
1099                    mAnimVel = 0;
1100                }
1101            }
1102            else {
1103                // We are expanded and are now going to animate away.
1104                mAnimAccel = -2000.0f;
1105                if (vel > 0) {
1106                    mAnimVel = 0;
1107                }
1108            }
1109        } else {
1110            if (always || (
1111                    vel > 200.0f
1112                    || (y > (mDisplayHeight/2) && vel > -200.0f))) {
1113                // We are collapsed, and they moved enough to allow us to
1114                // expand.  Animate in the notifications.
1115                mAnimAccel = 2000.0f;
1116                if (vel < 0) {
1117                    mAnimVel = 0;
1118                }
1119            }
1120            else {
1121                // We are collapsed, but they didn't move sufficiently to cause
1122                // us to retract.  Animate back to the collapsed position.
1123                mAnimAccel = -2000.0f;
1124                if (vel > 0) {
1125                    mAnimVel = 0;
1126                }
1127            }
1128        }
1129        //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel
1130        //        + " mAnimAccel=" + mAnimAccel);
1131
1132        long now = SystemClock.uptimeMillis();
1133        mAnimLastTime = now;
1134        mCurAnimationTime = now + ANIM_FRAME_DURATION;
1135        mAnimating = true;
1136        mHandler.removeMessages(MSG_ANIMATE);
1137        mHandler.removeMessages(MSG_ANIMATE_REVEAL);
1138        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime);
1139        stopTracking();
1140    }
1141
1142    boolean interceptTouchEvent(MotionEvent event) {
1143        if (SPEW) {
1144            Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled="
1145                + mDisabled);
1146        }
1147
1148        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
1149            return false;
1150        }
1151
1152        final int statusBarSize = mStatusBarView.getHeight();
1153        final int hitSize = statusBarSize*2;
1154        if (event.getAction() == MotionEvent.ACTION_DOWN) {
1155            final int y = (int)event.getRawY();
1156
1157            if (!mExpanded) {
1158                mViewDelta = statusBarSize - y;
1159            } else {
1160                mTrackingView.getLocationOnScreen(mAbsPos);
1161                mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y;
1162            }
1163            if ((!mExpanded && y < hitSize) ||
1164                    (mExpanded && y > (mDisplay.getHeight()-hitSize))) {
1165
1166                // We drop events at the edge of the screen to make the windowshade come
1167                // down by accident less, especially when pushing open a device with a keyboard
1168                // that rotates (like g1 and droid)
1169                int x = (int)event.getRawX();
1170                final int edgeBorder = mEdgeBorder;
1171                if (x >= edgeBorder && x < mDisplay.getWidth() - edgeBorder) {
1172                    prepareTracking(y, !mExpanded);// opening if we're not already fully visible
1173                    mVelocityTracker.addMovement(event);
1174                }
1175            }
1176        } else if (mTracking) {
1177            mVelocityTracker.addMovement(event);
1178            final int minY = statusBarSize + mCloseView.getHeight();
1179            if (event.getAction() == MotionEvent.ACTION_MOVE) {
1180                int y = (int)event.getRawY();
1181                if (mAnimatingReveal && y < minY) {
1182                    // nothing
1183                } else  {
1184                    mAnimatingReveal = false;
1185                    updateExpandedViewPos(y + mViewDelta);
1186                }
1187            } else if (event.getAction() == MotionEvent.ACTION_UP) {
1188                mVelocityTracker.computeCurrentVelocity(1000);
1189
1190                float yVel = mVelocityTracker.getYVelocity();
1191                boolean negative = yVel < 0;
1192
1193                float xVel = mVelocityTracker.getXVelocity();
1194                if (xVel < 0) {
1195                    xVel = -xVel;
1196                }
1197                if (xVel > 150.0f) {
1198                    xVel = 150.0f; // limit how much we care about the x axis
1199                }
1200
1201                float vel = (float)Math.hypot(yVel, xVel);
1202                if (negative) {
1203                    vel = -vel;
1204                }
1205
1206                performFling((int)event.getRawY(), vel, false);
1207            }
1208
1209        }
1210        return false;
1211    }
1212
1213    public void setLightsOn(boolean on) {
1214        Log.v(TAG, "lights " + (on ? "on" : "off"));
1215        if (!on) {
1216            // All we do for "lights out" mode on a phone is hide the status bar,
1217            // which the window manager does.  But we do need to hide the windowshade
1218            // on our own.
1219            animateCollapse();
1220        }
1221        notifyLightsChanged(on);
1222    }
1223
1224    private void notifyLightsChanged(boolean shown) {
1225        try {
1226            Slog.d(TAG, "lights " + (shown?"on":"out"));
1227            mWindowManager.statusBarVisibilityChanged(
1228                    shown ? View.STATUS_BAR_VISIBLE : View.STATUS_BAR_HIDDEN);
1229        } catch (RemoteException ex) {
1230        }
1231    }
1232
1233    // Not supported
1234    public void topAppWindowChanged(boolean visible) { }
1235    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { }
1236    @Override
1237    public void setHardKeyboardStatus(boolean available, boolean enabled) { }
1238
1239    private class Launcher implements View.OnClickListener {
1240        private PendingIntent mIntent;
1241        private String mPkg;
1242        private String mTag;
1243        private int mId;
1244
1245        Launcher(PendingIntent intent, String pkg, String tag, int id) {
1246            mIntent = intent;
1247            mPkg = pkg;
1248            mTag = tag;
1249            mId = id;
1250        }
1251
1252        public void onClick(View v) {
1253            try {
1254                // The intent we are sending is for the application, which
1255                // won't have permission to immediately start an activity after
1256                // the user switches to home.  We know it is safe to do at this
1257                // point, so make sure new activity switches are now allowed.
1258                ActivityManagerNative.getDefault().resumeAppSwitches();
1259            } catch (RemoteException e) {
1260            }
1261
1262            if (mIntent != null) {
1263                int[] pos = new int[2];
1264                v.getLocationOnScreen(pos);
1265                Intent overlay = new Intent();
1266                overlay.setSourceBounds(
1267                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
1268                try {
1269                    mIntent.send(mContext, 0, overlay);
1270                } catch (PendingIntent.CanceledException e) {
1271                    // the stack trace isn't very helpful here.  Just log the exception message.
1272                    Slog.w(TAG, "Sending contentIntent failed: " + e);
1273                }
1274            }
1275
1276            try {
1277                mBarService.onNotificationClick(mPkg, mTag, mId);
1278            } catch (RemoteException ex) {
1279                // system process is dead if we're here.
1280            }
1281
1282            // close the shade if it was open
1283            animateCollapse();
1284
1285            // If this click was on the intruder alert, hide that instead
1286            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
1287        }
1288    }
1289
1290    private void tick(StatusBarNotification n) {
1291        // Show the ticker if one is requested. Also don't do this
1292        // until status bar window is attached to the window manager,
1293        // because...  well, what's the point otherwise?  And trying to
1294        // run a ticker without being attached will crash!
1295        if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) {
1296            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
1297                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
1298                mTicker.addEntry(n);
1299            }
1300        }
1301    }
1302
1303    /**
1304     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
1305     * about the failure.
1306     *
1307     * WARNING: this will call back into us.  Don't hold any locks.
1308     */
1309    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
1310        removeNotification(key);
1311        try {
1312            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
1313        } catch (RemoteException ex) {
1314            // The end is nigh.
1315        }
1316    }
1317
1318    private class MyTicker extends Ticker {
1319        MyTicker(Context context, View sb) {
1320            super(context, sb);
1321        }
1322
1323        @Override
1324        public void tickerStarting() {
1325            mTicking = true;
1326            mIcons.setVisibility(View.GONE);
1327            mTickerView.setVisibility(View.VISIBLE);
1328            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null));
1329            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null));
1330            if (mExpandedVisible) {
1331                setDateViewVisibility(false, com.android.internal.R.anim.push_up_out);
1332            }
1333        }
1334
1335        @Override
1336        public void tickerDone() {
1337            mIcons.setVisibility(View.VISIBLE);
1338            mTickerView.setVisibility(View.GONE);
1339            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
1340            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out,
1341                        mTickingDoneListener));
1342            if (mExpandedVisible) {
1343                setDateViewVisibility(true, com.android.internal.R.anim.push_down_in);
1344            }
1345        }
1346
1347        public void tickerHalting() {
1348            mIcons.setVisibility(View.VISIBLE);
1349            mTickerView.setVisibility(View.GONE);
1350            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
1351            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out,
1352                        mTickingDoneListener));
1353            if (mExpandedVisible) {
1354                setDateViewVisibility(true, com.android.internal.R.anim.fade_in);
1355            }
1356        }
1357    }
1358
1359    Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {;
1360        public void onAnimationEnd(Animation animation) {
1361            mTicking = false;
1362        }
1363        public void onAnimationRepeat(Animation animation) {
1364        }
1365        public void onAnimationStart(Animation animation) {
1366        }
1367    };
1368
1369    private Animation loadAnim(int id, Animation.AnimationListener listener) {
1370        Animation anim = AnimationUtils.loadAnimation(mContext, id);
1371        if (listener != null) {
1372            anim.setAnimationListener(listener);
1373        }
1374        return anim;
1375    }
1376
1377    public String viewInfo(View v) {
1378        return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom()
1379                + " " + v.getWidth() + "x" + v.getHeight() + ")";
1380    }
1381
1382    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1383        synchronized (mQueueLock) {
1384            pw.println("Current Status Bar state:");
1385            pw.println("  mExpanded=" + mExpanded
1386                    + ", mExpandedVisible=" + mExpandedVisible);
1387            pw.println("  mTicking=" + mTicking);
1388            pw.println("  mTracking=" + mTracking);
1389            pw.println("  mAnimating=" + mAnimating
1390                    + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel
1391                    + ", mAnimAccel=" + mAnimAccel);
1392            pw.println("  mCurAnimationTime=" + mCurAnimationTime
1393                    + " mAnimLastTime=" + mAnimLastTime);
1394            pw.println("  mDisplayHeight=" + mDisplayHeight
1395                    + " mAnimatingReveal=" + mAnimatingReveal
1396                    + " mViewDelta=" + mViewDelta);
1397            pw.println("  mDisplayHeight=" + mDisplayHeight);
1398            pw.println("  mExpandedParams: " + mExpandedParams);
1399            pw.println("  mExpandedView: " + viewInfo(mExpandedView));
1400            pw.println("  mExpandedDialog: " + mExpandedDialog);
1401            pw.println("  mTrackingParams: " + mTrackingParams);
1402            pw.println("  mTrackingView: " + viewInfo(mTrackingView));
1403            pw.println("  mOngoingTitle: " + viewInfo(mOngoingTitle));
1404            pw.println("  mOngoingItems: " + viewInfo(mOngoingItems));
1405            pw.println("  mLatestTitle: " + viewInfo(mLatestTitle));
1406            pw.println("  mLatestItems: " + viewInfo(mLatestItems));
1407            pw.println("  mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle));
1408            pw.println("  mCloseView: " + viewInfo(mCloseView));
1409            pw.println("  mTickerView: " + viewInfo(mTickerView));
1410            pw.println("  mScrollView: " + viewInfo(mScrollView)
1411                    + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY());
1412            pw.println("mNotificationLinearLayout: " + viewInfo(mNotificationLinearLayout));
1413        }
1414        /*
1415        synchronized (mNotificationData) {
1416            int N = mNotificationData.ongoingCount();
1417            pw.println("  ongoingCount.size=" + N);
1418            for (int i=0; i<N; i++) {
1419                StatusBarNotification n = mNotificationData.getOngoing(i);
1420                pw.println("    [" + i + "] key=" + n.key + " view=" + n.view);
1421                pw.println("           data=" + n.data);
1422            }
1423            N = mNotificationData.latestCount();
1424            pw.println("  ongoingCount.size=" + N);
1425            for (int i=0; i<N; i++) {
1426                StatusBarNotification n = mNotificationData.getLatest(i);
1427                pw.println("    [" + i + "] key=" + n.key + " view=" + n.view);
1428                pw.println("           data=" + n.data);
1429            }
1430        }
1431        */
1432
1433        if (false) {
1434            pw.println("see the logcat for a dump of the views we have created.");
1435            // must happen on ui thread
1436            mHandler.post(new Runnable() {
1437                    public void run() {
1438                        mStatusBarView.getLocationOnScreen(mAbsPos);
1439                        Slog.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
1440                                + ") " + mStatusBarView.getWidth() + "x"
1441                                + mStatusBarView.getHeight());
1442                        mStatusBarView.debug();
1443
1444                        mExpandedView.getLocationOnScreen(mAbsPos);
1445                        Slog.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
1446                                + ") " + mExpandedView.getWidth() + "x"
1447                                + mExpandedView.getHeight());
1448                        mExpandedView.debug();
1449
1450                        mTrackingView.getLocationOnScreen(mAbsPos);
1451                        Slog.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
1452                                + ") " + mTrackingView.getWidth() + "x"
1453                                + mTrackingView.getHeight());
1454                        mTrackingView.debug();
1455                    }
1456                });
1457        }
1458    }
1459
1460    void onBarViewAttached() {
1461        WindowManager.LayoutParams lp;
1462        int pixelFormat;
1463        Drawable bg;
1464
1465        /// ---------- Tracking View --------------
1466        pixelFormat = PixelFormat.RGBX_8888;
1467        bg = mTrackingView.getBackground();
1468        if (bg != null) {
1469            pixelFormat = bg.getOpacity();
1470        }
1471
1472        lp = new WindowManager.LayoutParams(
1473                ViewGroup.LayoutParams.MATCH_PARENT,
1474                ViewGroup.LayoutParams.MATCH_PARENT,
1475                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
1476                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1477                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
1478                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
1479                pixelFormat);
1480//        lp.token = mStatusBarView.getWindowToken();
1481        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
1482        lp.setTitle("TrackingView");
1483        lp.y = mTrackingPosition;
1484        mTrackingParams = lp;
1485
1486        WindowManagerImpl.getDefault().addView(mTrackingView, lp);
1487    }
1488
1489    void onTrackingViewAttached() {
1490        WindowManager.LayoutParams lp;
1491        int pixelFormat;
1492        Drawable bg;
1493
1494        /// ---------- Expanded View --------------
1495        pixelFormat = PixelFormat.TRANSLUCENT;
1496
1497        final int disph = mDisplay.getHeight();
1498        lp = mExpandedDialog.getWindow().getAttributes();
1499        lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
1500        lp.height = getExpandedHeight();
1501        lp.x = 0;
1502        mTrackingPosition = lp.y = -disph; // sufficiently large negative
1503        lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
1504        lp.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1505                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
1506                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1507                | WindowManager.LayoutParams.FLAG_DITHER
1508                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1509        lp.format = pixelFormat;
1510        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
1511        lp.setTitle("StatusBarExpanded");
1512        mExpandedDialog.getWindow().setAttributes(lp);
1513        mExpandedDialog.getWindow().setFormat(pixelFormat);
1514        mExpandedParams = lp;
1515
1516        mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
1517        mExpandedDialog.setContentView(mExpandedView,
1518                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1519                                           ViewGroup.LayoutParams.MATCH_PARENT));
1520        mExpandedDialog.getWindow().setBackgroundDrawable(null);
1521        mExpandedDialog.show();
1522        FrameLayout hack = (FrameLayout)mExpandedView.getParent();
1523    }
1524
1525    void setDateViewVisibility(boolean visible, int anim) {
1526        mDateView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1527        mDateView.startAnimation(loadAnim(anim, null));
1528    }
1529
1530    void setNotificationIconVisibility(boolean visible, int anim) {
1531        int old = mNotificationIcons.getVisibility();
1532        int v = visible ? View.VISIBLE : View.INVISIBLE;
1533        if (old != v) {
1534            mNotificationIcons.setVisibility(v);
1535            mNotificationIcons.startAnimation(loadAnim(anim, null));
1536        }
1537    }
1538
1539    void updateExpandedViewPos(int expandedPosition) {
1540        if (SPEW) {
1541            Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition
1542                    + " mTrackingParams.y=" + mTrackingParams.y
1543                    + " mTrackingPosition=" + mTrackingPosition);
1544        }
1545
1546        int h = mStatusBarView.getHeight();
1547        int disph = mDisplay.getHeight();
1548
1549        // If the expanded view is not visible, make sure they're still off screen.
1550        // Maybe the view was resized.
1551        if (!mExpandedVisible) {
1552            if (mTrackingView != null) {
1553                mTrackingPosition = -disph;
1554                if (mTrackingParams != null) {
1555                    mTrackingParams.y = mTrackingPosition;
1556                    WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
1557                }
1558            }
1559            if (mExpandedParams != null) {
1560                mExpandedParams.y = -disph;
1561                mExpandedDialog.getWindow().setAttributes(mExpandedParams);
1562            }
1563            return;
1564        }
1565
1566        // tracking view...
1567        int pos;
1568        if (expandedPosition == EXPANDED_FULL_OPEN) {
1569            pos = h;
1570        }
1571        else if (expandedPosition == EXPANDED_LEAVE_ALONE) {
1572            pos = mTrackingPosition;
1573        }
1574        else {
1575            if (expandedPosition <= disph) {
1576                pos = expandedPosition;
1577            } else {
1578                pos = disph;
1579            }
1580            pos -= disph-h;
1581        }
1582        mTrackingPosition = mTrackingParams.y = pos;
1583        mTrackingParams.height = disph-h;
1584        WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
1585
1586        if (mExpandedParams != null) {
1587            mCloseView.getLocationInWindow(mPositionTmp);
1588            final int closePos = mPositionTmp[1];
1589
1590            mExpandedContents.getLocationInWindow(mPositionTmp);
1591            final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight();
1592
1593            mExpandedParams.y = pos + mTrackingView.getHeight()
1594                    - (mTrackingParams.height-closePos) - contentsBottom;
1595            int max = h;
1596            if (mExpandedParams.y > max) {
1597                mExpandedParams.y = max;
1598            }
1599            int min = mTrackingPosition;
1600            if (mExpandedParams.y < min) {
1601                mExpandedParams.y = min;
1602            }
1603
1604            boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h;
1605            if (!visible) {
1606                // if the contents aren't visible, move the expanded view way off screen
1607                // because the window itself extends below the content view.
1608                mExpandedParams.y = -disph;
1609            }
1610            mExpandedDialog.getWindow().setAttributes(mExpandedParams);
1611
1612            // As long as this isn't just a repositioning that's not supposed to affect
1613            // the user's perception of what's showing, call to say that the visibility
1614            // has changed. (Otherwise, someone else will call to do that).
1615            if (expandedPosition != EXPANDED_LEAVE_ALONE) {
1616                if (SPEW) Slog.d(TAG, "updateExpandedViewPos visibilityChanged(" + visible + ")");
1617                visibilityChanged(visible);
1618            }
1619        }
1620
1621        if (SPEW) {
1622            Slog.d(TAG, "updateExpandedViewPos after  expandedPosition=" + expandedPosition
1623                    + " mTrackingParams.y=" + mTrackingParams.y
1624                    + " mTrackingPosition=" + mTrackingPosition
1625                    + " mExpandedParams.y=" + mExpandedParams.y
1626                    + " mExpandedParams.height=" + mExpandedParams.height);
1627        }
1628    }
1629
1630    int getExpandedHeight() {
1631        return mDisplay.getHeight() - mStatusBarView.getHeight() - mCloseView.getHeight();
1632    }
1633
1634    void updateExpandedHeight() {
1635        if (mExpandedView != null) {
1636            mExpandedParams.height = getExpandedHeight();
1637            mExpandedDialog.getWindow().setAttributes(mExpandedParams);
1638        }
1639    }
1640
1641    public void userActivity() {
1642        try {
1643            mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
1644        } catch (RemoteException ex) { }
1645    }
1646
1647    public void toggleRecentApps() {
1648        int msg = (mRecentsPanel.getVisibility() == View.GONE)
1649                ? MSG_OPEN_RECENTS_PANEL : MSG_CLOSE_RECENTS_PANEL;
1650        mHandler.removeMessages(msg);
1651        mHandler.sendEmptyMessage(msg);
1652    }
1653
1654    /**
1655     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
1656     * This was added last-minute and is inconsistent with the way the rest of the notifications
1657     * are handled, because the notification isn't really cancelled.  The lights are just
1658     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
1659     * this is what he wants. (see bug 1131461)
1660     */
1661    void visibilityChanged(boolean visible) {
1662        if (mPanelSlightlyVisible != visible) {
1663            mPanelSlightlyVisible = visible;
1664            try {
1665                mBarService.onPanelRevealed();
1666            } catch (RemoteException ex) {
1667                // Won't fail unless the world has ended.
1668            }
1669        }
1670    }
1671
1672    void performDisableActions(int net) {
1673        int old = mDisabled;
1674        int diff = net ^ old;
1675        mDisabled = net;
1676
1677        // act accordingly
1678        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
1679            if ((net & StatusBarManager.DISABLE_EXPAND) != 0) {
1680                Slog.d(TAG, "DISABLE_EXPAND: yes");
1681                animateCollapse();
1682            }
1683        }
1684        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
1685            if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
1686                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
1687                if (mTicking) {
1688                    mNotificationIcons.setVisibility(View.INVISIBLE);
1689                    mTicker.halt();
1690                } else {
1691                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
1692                }
1693            } else {
1694                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
1695                if (!mExpandedVisible) {
1696                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
1697                }
1698            }
1699        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
1700            if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
1701                mTicker.halt();
1702            }
1703        }
1704    }
1705
1706    private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
1707        public void onClick(View v) {
1708            try {
1709                mBarService.onClearAllNotifications();
1710            } catch (RemoteException ex) {
1711                // system process is dead if we're here.
1712            }
1713            animateCollapse();
1714        }
1715    };
1716
1717    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1718        public void onReceive(Context context, Intent intent) {
1719            String action = intent.getAction();
1720            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
1721                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
1722                boolean excludeRecents = false;
1723                if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
1724                    String reason = intent.getStringExtra("reason");
1725                    if (reason != null) {
1726                        excludeRecents = reason.equals("recentapps");
1727                    }
1728                }
1729                animateCollapse(excludeRecents);
1730            }
1731            else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
1732                repositionNavigationBar();
1733                updateResources();
1734            }
1735        }
1736    };
1737
1738    private void setIntruderAlertVisibility(boolean vis) {
1739        mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE);
1740    }
1741
1742    /**
1743     * Reload some of our resources when the configuration changes.
1744     *
1745     * We don't reload everything when the configuration changes -- we probably
1746     * should, but getting that smooth is tough.  Someday we'll fix that.  In the
1747     * meantime, just update the things that we know change.
1748     */
1749    void updateResources() {
1750        final Context context = mContext;
1751        final Resources res = context.getResources();
1752
1753        mClearButton.setText(context.getText(R.string.status_bar_clear_all_button));
1754        mOngoingTitle.setText(context.getText(R.string.status_bar_ongoing_events_title));
1755        mLatestTitle.setText(context.getText(R.string.status_bar_latest_events_title));
1756        mNoNotificationsTitle.setText(context.getText(R.string.status_bar_no_notifications_title));
1757
1758        mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
1759
1760        if (false) Slog.v(TAG, "updateResources");
1761    }
1762
1763    //
1764    // tracing
1765    //
1766
1767    void postStartTracing() {
1768        mHandler.postDelayed(mStartTracing, 3000);
1769    }
1770
1771    void vibrate() {
1772        android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService(
1773                Context.VIBRATOR_SERVICE);
1774        vib.vibrate(250);
1775    }
1776
1777    Runnable mStartTracing = new Runnable() {
1778        public void run() {
1779            vibrate();
1780            SystemClock.sleep(250);
1781            Slog.d(TAG, "startTracing");
1782            android.os.Debug.startMethodTracing("/data/statusbar-traces/trace");
1783            mHandler.postDelayed(mStopTracing, 10000);
1784        }
1785    };
1786
1787    Runnable mStopTracing = new Runnable() {
1788        public void run() {
1789            android.os.Debug.stopMethodTracing();
1790            Slog.d(TAG, "stopTracing");
1791            vibrate();
1792        }
1793    };
1794
1795    public class TouchOutsideListener implements View.OnTouchListener {
1796        private int mMsg;
1797        private RecentsPanelView mPanel;
1798
1799        public TouchOutsideListener(int msg, RecentsPanelView panel) {
1800            mMsg = msg;
1801            mPanel = panel;
1802        }
1803
1804        public boolean onTouch(View v, MotionEvent ev) {
1805            final int action = ev.getAction();
1806            if (action == MotionEvent.ACTION_OUTSIDE
1807                || (action == MotionEvent.ACTION_DOWN
1808                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
1809                mHandler.removeMessages(mMsg);
1810                mHandler.sendEmptyMessage(mMsg);
1811                return true;
1812            }
1813            return false;
1814        }
1815    }
1816}
1817
1818