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