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