PhoneStatusBar.java revision 84e825a84d7dde03042228c79638935c92d13c92
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.animation.ObjectAnimator;
20import android.app.ActivityManager;
21import android.app.ActivityManagerNative;
22import android.app.Dialog;
23import android.app.KeyguardManager;
24import android.app.Notification;
25import android.app.PendingIntent;
26import android.app.StatusBarManager;
27import android.content.BroadcastReceiver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.res.Resources;
32import android.content.res.Configuration;
33import android.graphics.PixelFormat;
34import android.graphics.Rect;
35import android.graphics.drawable.Drawable;
36import android.os.IBinder;
37import android.os.RemoteException;
38import android.os.Handler;
39import android.os.Message;
40import android.os.ServiceManager;
41import android.os.SystemClock;
42import android.provider.Settings;
43import android.text.TextUtils;
44import android.util.DisplayMetrics;
45import android.util.Slog;
46import android.util.Log;
47import android.view.Display;
48import android.view.Gravity;
49import android.view.IWindowManager;
50import android.view.KeyEvent;
51import android.view.LayoutInflater;
52import android.view.MotionEvent;
53import android.view.VelocityTracker;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.ViewGroup.LayoutParams;
57import android.view.Window;
58import android.view.WindowManager;
59import android.view.WindowManagerImpl;
60import android.view.animation.Animation;
61import android.view.animation.AnimationUtils;
62import android.widget.ImageView;
63import android.widget.LinearLayout;
64import android.widget.RemoteViews;
65import android.widget.ScrollView;
66import android.widget.TextView;
67
68import java.io.FileDescriptor;
69import java.io.PrintWriter;
70import java.util.ArrayList;
71
72import com.android.internal.statusbar.StatusBarIcon;
73import com.android.internal.statusbar.StatusBarNotification;
74
75import com.android.systemui.R;
76import com.android.systemui.recent.RecentTasksLoader;
77import com.android.systemui.recent.RecentsPanelView;
78import com.android.systemui.recent.TaskDescription;
79import com.android.systemui.statusbar.NotificationData;
80import com.android.systemui.statusbar.StatusBar;
81import com.android.systemui.statusbar.StatusBarIconView;
82import com.android.systemui.statusbar.SignalClusterView;
83import com.android.systemui.statusbar.policy.DateView;
84import com.android.systemui.statusbar.policy.BatteryController;
85import com.android.systemui.statusbar.policy.LocationController;
86import com.android.systemui.statusbar.policy.NetworkController;
87import com.android.systemui.statusbar.policy.NotificationRowLayout;
88
89public class PhoneStatusBar extends StatusBar {
90    static final String TAG = "PhoneStatusBar";
91    public static final boolean DEBUG = false;
92    public static final boolean SPEW = false;
93    public static final boolean DUMPTRUCK = true; // extra dumpsys info
94
95    // additional instrumentation for testing purposes; intended to be left on during development
96    public static final boolean CHATTY = DEBUG;
97
98    public static final String ACTION_STATUSBAR_START
99            = "com.android.internal.policy.statusbar.START";
100
101    static final int EXPANDED_LEAVE_ALONE = -10000;
102    static final int EXPANDED_FULL_OPEN = -10001;
103
104    private static final int MSG_ANIMATE = 100;
105    private static final int MSG_ANIMATE_REVEAL = 101;
106    private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
107    private static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
108    private static final int MSG_SHOW_INTRUDER = 1002;
109    private static final int MSG_HIDE_INTRUDER = 1003;
110    private static final int MSG_OPEN_RECENTS_PANEL = 1020;
111    private static final int MSG_CLOSE_RECENTS_PANEL = 1021;
112
113    // will likely move to a resource or other tunable param at some point
114    private static final int INTRUDER_ALERT_DECAY_MS = 10000;
115
116    private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
117
118    // fling gesture tuning parameters, scaled to display density
119    private float mSelfExpandVelocityPx; // classic value: 2000px/s
120    private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
121    private float mFlingExpandMinVelocityPx; // classic value: 200px/s
122    private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
123    private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
124    private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
125    private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
126
127    private float mExpandAccelPx; // classic value: 2000px/s/s
128    private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
129
130    PhoneStatusBarPolicy mIconPolicy;
131
132    // These are no longer handled by the policy, because we need custom strategies for them
133    BatteryController mBatteryController;
134    LocationController mLocationController;
135    NetworkController mNetworkController;
136
137    int mNaturalBarHeight = -1;
138    int mIconSize = -1;
139    int mIconHPadding = -1;
140    Display mDisplay;
141
142    IWindowManager mWindowManager;
143
144    PhoneStatusBarView mStatusBarView;
145    int mPixelFormat;
146    H mHandler = new H();
147    Object mQueueLock = new Object();
148
149    // icons
150    LinearLayout mIcons;
151    IconMerger mNotificationIcons;
152    LinearLayout mStatusIcons;
153
154    // expanded notifications
155    Dialog mExpandedDialog;
156    ExpandedView mExpandedView;
157    WindowManager.LayoutParams mExpandedParams;
158    ScrollView mScrollView;
159    View mExpandedContents;
160    // top bar
161    TextView mNoNotificationsTitle;
162    View mClearButton;
163    View mSettingsButton;
164
165    // drag bar
166    CloseDragHandle mCloseView;
167
168    // all notifications
169    NotificationData mNotificationData = new NotificationData();
170    NotificationRowLayout mPile;
171
172    // position
173    int[] mPositionTmp = new int[2];
174    boolean mExpanded;
175    boolean mExpandedVisible;
176
177    // the date view
178    DateView mDateView;
179
180    // for immersive activities
181    private View mIntruderAlertView;
182
183    // on-screen navigation buttons
184    private NavigationBarView mNavigationBarView = null;
185
186    // the tracker view
187    TrackingView mTrackingView;
188    WindowManager.LayoutParams mTrackingParams;
189    int mTrackingPosition; // the position of the top of the tracking view.
190    private boolean mPanelSlightlyVisible;
191
192    // ticker
193    private Ticker mTicker;
194    private View mTickerView;
195    private boolean mTicking;
196
197    // Recent apps
198    private RecentsPanelView mRecentsPanel;
199    private RecentTasksLoader mRecentTasksLoader;
200
201    // Tracking finger for opening/closing.
202    int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore
203    boolean mTracking;
204    VelocityTracker mVelocityTracker;
205
206    static final int ANIM_FRAME_DURATION = (1000/60);
207
208    boolean mAnimating;
209    long mCurAnimationTime;
210    float mAnimY;
211    float mAnimVel;
212    float mAnimAccel;
213    long mAnimLastTime;
214    boolean mAnimatingReveal = false;
215    int mViewDelta;
216    int[] mAbsPos = new int[2];
217    Runnable mPostCollapseCleanup = null;
218
219
220    // for disabling the status bar
221    int mDisabled = 0;
222
223    // tracking calls to View.setSystemUiVisibility()
224    int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
225
226    DisplayMetrics mDisplayMetrics = new DisplayMetrics();
227
228    private class ExpandedDialog extends Dialog {
229        ExpandedDialog(Context context) {
230            super(context, com.android.internal.R.style.Theme_Translucent_NoTitleBar);
231        }
232
233        @Override
234        public boolean dispatchKeyEvent(KeyEvent event) {
235            boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
236            switch (event.getKeyCode()) {
237            case KeyEvent.KEYCODE_BACK:
238                if (!down) {
239                    animateCollapse();
240                }
241                return true;
242            }
243            return super.dispatchKeyEvent(event);
244        }
245    }
246
247    @Override
248    public void start() {
249        mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
250                .getDefaultDisplay();
251
252        mWindowManager = IWindowManager.Stub.asInterface(
253                ServiceManager.getService(Context.WINDOW_SERVICE));
254
255        super.start();
256
257        addNavigationBar();
258
259        //addIntruderView();
260
261        // Lastly, call to the icon policy to install/update all the icons.
262        mIconPolicy = new PhoneStatusBarPolicy(mContext);
263    }
264
265    // ================================================================================
266    // Constructing the view
267    // ================================================================================
268    protected View makeStatusBarView() {
269        final Context context = mContext;
270
271        Resources res = context.getResources();
272
273        mDisplay.getMetrics(mDisplayMetrics);
274        if (DEBUG) {
275            Slog.d(TAG, "makeStatusBarView: mDisplayMetrics=" + mDisplayMetrics);
276            mDisplayMetrics = res.getDisplayMetrics();
277            Slog.d(TAG, "makeStatusBarView: mDisplayMetrics2=" + mDisplayMetrics);
278        }
279        loadDimens();
280
281        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
282
283        ExpandedView expanded = (ExpandedView)View.inflate(context,
284                R.layout.status_bar_expanded, null);
285        if (DEBUG) {
286            expanded.setBackgroundColor(0x6000FF80);
287        }
288        expanded.mService = this;
289
290        mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null);
291        mIntruderAlertView.setVisibility(View.GONE);
292        mIntruderAlertView.setClickable(true);
293
294        PhoneStatusBarView sb = (PhoneStatusBarView)View.inflate(context,
295                R.layout.status_bar, null);
296        sb.mService = this;
297        mStatusBarView = sb;
298
299        try {
300            boolean showNav = mWindowManager.hasNavigationBar();
301            if (showNav) {
302                mNavigationBarView =
303                    (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);
304
305                mNavigationBarView.setDisabledFlags(mDisabled);
306            }
307        } catch (RemoteException ex) {
308            // no window manager? good luck with that
309        }
310
311        // figure out which pixel-format to use for the status bar.
312        mPixelFormat = PixelFormat.OPAQUE;
313        mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons);
314        mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons);
315        mIcons = (LinearLayout)sb.findViewById(R.id.icons);
316        mTickerView = sb.findViewById(R.id.ticker);
317
318        mExpandedDialog = new ExpandedDialog(context);
319        mExpandedView = expanded;
320        mPile = (NotificationRowLayout)expanded.findViewById(R.id.latestItems);
321        mExpandedContents = mPile; // was: expanded.findViewById(R.id.notificationLinearLayout);
322        mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle);
323        mNoNotificationsTitle.setVisibility(View.GONE); // disabling for now
324
325        mClearButton = expanded.findViewById(R.id.clear_all_button);
326        mClearButton.setOnClickListener(mClearButtonListener);
327        mClearButton.setAlpha(0f);
328        mClearButton.setEnabled(false);
329        mDateView = (DateView)expanded.findViewById(R.id.date);
330        mSettingsButton = expanded.findViewById(R.id.settings_button);
331        mSettingsButton.setOnClickListener(mSettingsButtonListener);
332        mScrollView = (ScrollView)expanded.findViewById(R.id.scroll);
333
334        mTicker = new MyTicker(context, sb);
335
336        TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText);
337        tickerView.mTicker = mTicker;
338
339        mTrackingView = (TrackingView)View.inflate(context, R.layout.status_bar_tracking, null);
340        mTrackingView.mService = this;
341        mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close);
342        mCloseView.mService = this;
343
344        mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
345
346        // set the inital view visibility
347        setAreThereNotifications();
348
349        // Other icons
350        mLocationController = new LocationController(mContext); // will post a notification
351        mBatteryController = new BatteryController(mContext);
352        mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
353        mNetworkController = new NetworkController(mContext);
354        final SignalClusterView signalCluster =
355                (SignalClusterView)sb.findViewById(R.id.signal_cluster);
356        mNetworkController.addSignalCluster(signalCluster);
357        signalCluster.setNetworkController(mNetworkController);
358
359        // Recents Panel
360        mRecentTasksLoader = new RecentTasksLoader(context);
361        updateRecentsPanel();
362
363        // receive broadcasts
364        IntentFilter filter = new IntentFilter();
365        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
366        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
367        filter.addAction(Intent.ACTION_SCREEN_OFF);
368        context.registerReceiver(mBroadcastReceiver, filter);
369
370        return sb;
371    }
372
373    protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) {
374        boolean opaque = false;
375        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
376                layoutParams.width,
377                layoutParams.height,
378                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
379                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
380                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
381                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
382                (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT));
383        if (ActivityManager.isHighEndGfx(mDisplay)) {
384            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
385        }
386        lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
387        lp.setTitle("RecentsPanel");
388        lp.windowAnimations = R.style.Animation_RecentPanel;
389        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
390        | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
391        return lp;
392    }
393
394    protected void updateRecentsPanel() {
395        // Recents Panel
396        boolean visible = false;
397        ArrayList<TaskDescription> recentTasksList = null;
398        if (mRecentsPanel != null) {
399            visible = mRecentsPanel.isShowing();
400            WindowManagerImpl.getDefault().removeView(mRecentsPanel);
401            if (visible) {
402                recentTasksList = mRecentsPanel.getRecentTasksList();
403            }
404        }
405
406        // Provide RecentsPanelView with a temporary parent to allow layout params to work.
407        LinearLayout tmpRoot = new LinearLayout(mContext);
408        mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate(
409                R.layout.status_bar_recent_panel, tmpRoot, false);
410        mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader);
411        mRecentTasksLoader.setRecentsPanel(mRecentsPanel);
412        mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
413                mRecentsPanel));
414        mRecentsPanel.setVisibility(View.GONE);
415        WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams());
416
417        WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
418        mRecentsPanel.setBar(this);
419        if (visible) {
420            mRecentsPanel.show(true, false, recentTasksList);
421        }
422
423    }
424
425    protected int getStatusBarGravity() {
426        return Gravity.TOP | Gravity.FILL_HORIZONTAL;
427    }
428
429    public int getStatusBarHeight() {
430        final Resources res = mContext.getResources();
431        return res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
432    }
433
434    private View.OnClickListener mRecentsClickListener = new View.OnClickListener() {
435        public void onClick(View v) {
436            toggleRecentApps();
437        }
438    };
439
440    private void prepareNavigationBarView() {
441        mNavigationBarView.reorient();
442
443        mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
444        mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPanel);
445    }
446
447    // For small-screen devices (read: phones) that lack hardware navigation buttons
448    private void addNavigationBar() {
449        if (mNavigationBarView == null) return;
450
451        prepareNavigationBarView();
452
453        WindowManagerImpl.getDefault().addView(
454                mNavigationBarView, getNavigationBarLayoutParams());
455    }
456
457    private void repositionNavigationBar() {
458        if (mNavigationBarView == null) return;
459
460        prepareNavigationBarView();
461
462        WindowManagerImpl.getDefault().updateViewLayout(
463                mNavigationBarView, getNavigationBarLayoutParams());
464    }
465
466    private WindowManager.LayoutParams getNavigationBarLayoutParams() {
467        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
468                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
469                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
470                    0
471                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
472                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
473                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
474                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
475                    | WindowManager.LayoutParams.FLAG_SLIPPERY,
476                PixelFormat.OPAQUE);
477        // this will allow the navbar to run in an overlay on devices that support this
478        if (ActivityManager.isHighEndGfx(mDisplay)) {
479            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
480        }
481
482        lp.setTitle("NavigationBar");
483        lp.windowAnimations = 0;
484
485        return lp;
486    }
487
488    private void addIntruderView() {
489        final int height = getStatusBarHeight();
490
491        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
492                ViewGroup.LayoutParams.MATCH_PARENT,
493                ViewGroup.LayoutParams.WRAP_CONTENT,
494                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
495                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
496                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
497                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
498                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
499                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
500                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
501                PixelFormat.TRANSLUCENT);
502        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
503        lp.y += height * 1.5; // FIXME
504        lp.setTitle("IntruderAlert");
505        lp.packageName = mContext.getPackageName();
506        lp.windowAnimations = R.style.Animation_StatusBar_IntruderAlert;
507
508        WindowManagerImpl.getDefault().addView(mIntruderAlertView, lp);
509    }
510
511    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
512        if (SPEW) Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
513                + " icon=" + icon);
514        StatusBarIconView view = new StatusBarIconView(mContext, slot, null);
515        view.set(icon);
516        mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize));
517    }
518
519    public void updateIcon(String slot, int index, int viewIndex,
520            StatusBarIcon old, StatusBarIcon icon) {
521        if (SPEW) Slog.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
522                + " old=" + old + " icon=" + icon);
523        StatusBarIconView view = (StatusBarIconView)mStatusIcons.getChildAt(viewIndex);
524        view.set(icon);
525    }
526
527    public void removeIcon(String slot, int index, int viewIndex) {
528        if (SPEW) Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex);
529        mStatusIcons.removeViewAt(viewIndex);
530    }
531
532    public void addNotification(IBinder key, StatusBarNotification notification) {
533        StatusBarIconView iconView = addNotificationViews(key, notification);
534        if (iconView == null) return;
535
536        boolean immersive = false;
537        try {
538            immersive = ActivityManagerNative.getDefault().isTopActivityImmersive();
539            if (DEBUG) {
540                Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
541            }
542        } catch (RemoteException ex) {
543        }
544        if (immersive) {
545            if ((notification.notification.flags & Notification.FLAG_HIGH_PRIORITY) != 0) {
546                Slog.d(TAG, "Presenting high-priority notification in immersive activity");
547                // special new transient ticker mode
548                // 1. Populate mIntruderAlertView
549
550                ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon);
551                TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText);
552                alertIcon.setImageDrawable(StatusBarIconView.getIcon(
553                    alertIcon.getContext(),
554                    iconView.getStatusBarIcon()));
555                alertText.setText(notification.notification.tickerText);
556
557                View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content);
558                button.setOnClickListener(
559                    new NotificationClicker(notification.notification.contentIntent,
560                        notification.pkg, notification.tag, notification.id));
561
562                // 2. Animate mIntruderAlertView in
563                mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER);
564
565                // 3. Set alarm to age the notification off (TODO)
566                mHandler.removeMessages(MSG_HIDE_INTRUDER);
567                mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
568            }
569        } else if (notification.notification.fullScreenIntent != null) {
570            // not immersive & a full-screen alert should be shown
571            Slog.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
572            try {
573                notification.notification.fullScreenIntent.send();
574            } catch (PendingIntent.CanceledException e) {
575            }
576        } else {
577            // usual case: status bar visible & not immersive
578
579            // show the ticker
580            tick(notification);
581        }
582
583        // Recalculate the position of the sliding windows and the titles.
584        setAreThereNotifications();
585        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
586    }
587
588    public void updateNotification(IBinder key, StatusBarNotification notification) {
589        if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
590
591        final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
592        if (oldEntry == null) {
593            Slog.w(TAG, "updateNotification for unknown key: " + key);
594            return;
595        }
596
597        final StatusBarNotification oldNotification = oldEntry.notification;
598        final RemoteViews oldContentView = oldNotification.notification.contentView;
599
600        final RemoteViews contentView = notification.notification.contentView;
601
602
603        if (DEBUG) {
604            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
605                    + " ongoing=" + oldNotification.isOngoing()
606                    + " expanded=" + oldEntry.expanded
607                    + " contentView=" + oldContentView
608                    + " rowParent=" + oldEntry.row.getParent());
609            Slog.d(TAG, "new notification: when=" + notification.notification.when
610                    + " ongoing=" + oldNotification.isOngoing()
611                    + " contentView=" + contentView);
612        }
613
614
615        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
616        // didn't change.
617        boolean contentsUnchanged = oldEntry.expanded != null
618                && contentView != null && oldContentView != null
619                && contentView.getPackage() != null
620                && oldContentView.getPackage() != null
621                && oldContentView.getPackage().equals(contentView.getPackage())
622                && oldContentView.getLayoutId() == contentView.getLayoutId();
623        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
624        boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
625                && notification.priority == oldNotification.priority;
626                // priority now encompasses isOngoing()
627
628        boolean updateTicker = notification.notification.tickerText != null
629                && !TextUtils.equals(notification.notification.tickerText,
630                        oldEntry.notification.notification.tickerText);
631        boolean isFirstAnyway = rowParent.indexOfChild(oldEntry.row) == 0;
632        if (contentsUnchanged && (orderUnchanged || isFirstAnyway)) {
633            if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
634            oldEntry.notification = notification;
635            try {
636                // Reapply the RemoteViews
637                contentView.reapply(mContext, oldEntry.content);
638                // update the contentIntent
639                final PendingIntent contentIntent = notification.notification.contentIntent;
640                if (contentIntent != null) {
641                    final View.OnClickListener listener = new NotificationClicker(contentIntent,
642                            notification.pkg, notification.tag, notification.id);
643                    oldEntry.largeIcon.setOnClickListener(listener);
644                    oldEntry.content.setOnClickListener(listener);
645                } else {
646                    oldEntry.largeIcon.setOnClickListener(null);
647                    oldEntry.content.setOnClickListener(null);
648                }
649                // Update the icon.
650                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
651                        notification.notification.icon, notification.notification.iconLevel,
652                        notification.notification.number,
653                        notification.notification.tickerText);
654                if (!oldEntry.icon.set(ic)) {
655                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
656                    return;
657                }
658                // Update the large icon
659                if (notification.notification.largeIcon != null) {
660                    oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon);
661                } else {
662                    oldEntry.largeIcon.getLayoutParams().width = 0;
663                    oldEntry.largeIcon.setVisibility(View.INVISIBLE);
664                }
665            }
666            catch (RuntimeException e) {
667                // It failed to add cleanly.  Log, and remove the view from the panel.
668                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
669                removeNotificationViews(key);
670                addNotificationViews(key, notification);
671            }
672        } else {
673            if (SPEW) Slog.d(TAG, "not reusing notification");
674            removeNotificationViews(key);
675            addNotificationViews(key, notification);
676        }
677
678        // Update the veto button accordingly (and as a result, whether this row is
679        // swipe-dismissable)
680        updateNotificationVetoButton(oldEntry.row, notification);
681
682        // Restart the ticker if it's still running
683        if (updateTicker) {
684            mTicker.halt();
685            tick(notification);
686        }
687
688        // Recalculate the position of the sliding windows and the titles.
689        setAreThereNotifications();
690        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
691    }
692
693    public void removeNotification(IBinder key) {
694        if (SPEW) Slog.d(TAG, "removeNotification key=" + key);
695        StatusBarNotification old = removeNotificationViews(key);
696
697        if (old != null) {
698            // Cancel the ticker if it's still running
699            mTicker.removeEntry(old);
700
701            // Recalculate the position of the sliding windows and the titles.
702            updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
703
704            if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0 && !mAnimating) {
705                animateCollapse();
706            }
707        }
708
709        setAreThereNotifications();
710    }
711
712    @Override
713    protected void onConfigurationChanged(Configuration newConfig) {
714        updateRecentsPanel();
715    }
716
717
718    View[] makeNotificationView(StatusBarNotification notification, ViewGroup parent) {
719        Notification n = notification.notification;
720        RemoteViews remoteViews = n.contentView;
721        if (remoteViews == null) {
722            return null;
723        }
724
725        // create the row view
726        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
727                Context.LAYOUT_INFLATER_SERVICE);
728        View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
729
730        // wire up the veto button
731        View vetoButton = updateNotificationVetoButton(row, notification);
732        vetoButton.setContentDescription(mContext.getString(
733                R.string.accessibility_remove_notification));
734
735        // the large icon
736        ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
737        if (notification.notification.largeIcon != null) {
738            largeIcon.setImageBitmap(notification.notification.largeIcon);
739        } else {
740            largeIcon.getLayoutParams().width = 0;
741            largeIcon.setVisibility(View.INVISIBLE);
742        }
743
744        // bind the click event to the content area
745        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
746        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
747        content.setOnFocusChangeListener(mFocusChangeListener);
748        PendingIntent contentIntent = n.contentIntent;
749        if (contentIntent != null) {
750            final View.OnClickListener listener = new NotificationClicker(contentIntent,
751                    notification.pkg, notification.tag, notification.id);
752            largeIcon.setOnClickListener(listener);
753            content.setOnClickListener(listener);
754        } else {
755            largeIcon.setOnClickListener(null);
756            content.setOnClickListener(null);
757        }
758
759        View expanded = null;
760        Exception exception = null;
761        try {
762            expanded = remoteViews.apply(mContext, content);
763        }
764        catch (RuntimeException e) {
765            exception = e;
766        }
767        if (expanded == null) {
768            String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id);
769            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
770            return null;
771        } else {
772            content.addView(expanded);
773            row.setDrawingCacheEnabled(true);
774        }
775
776        return new View[] { row, content, expanded };
777    }
778
779    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
780        if (DEBUG) {
781            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
782        }
783        // Construct the icon.
784        final StatusBarIconView iconView = new StatusBarIconView(mContext,
785                notification.pkg + "/0x" + Integer.toHexString(notification.id),
786                notification.notification);
787        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
788
789        final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
790                    notification.notification.icon,
791                    notification.notification.iconLevel,
792                    notification.notification.number,
793                    notification.notification.tickerText);
794        if (!iconView.set(ic)) {
795            handleNotificationError(key, notification, "Couldn't create icon: " + ic);
796            return null;
797        }
798        // Construct the expanded view.
799        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
800        if (!inflateViews(entry, mPile)) {
801            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
802                    + notification);
803            return null;
804        }
805
806        // Add the expanded view and icon.
807        int pos = mNotificationData.add(entry);
808        if (DEBUG) {
809            Slog.d(TAG, "addNotificationViews: added at " + pos);
810        }
811        updateNotificationIcons();
812
813        return iconView;
814    }
815
816    private void loadNotificationShade() {
817        int N = mNotificationData.size();
818
819        ArrayList<View> toShow = new ArrayList<View>();
820
821        for (int i=0; i<N; i++) {
822            View row = mNotificationData.get(N-i-1).row;
823            toShow.add(row);
824        }
825
826        ArrayList<View> toRemove = new ArrayList<View>();
827        for (int i=0; i<mPile.getChildCount(); i++) {
828            View child = mPile.getChildAt(i);
829            if (!toShow.contains(child)) {
830                toRemove.add(child);
831            }
832        }
833
834        for (View remove : toRemove) {
835            mPile.removeView(remove);
836        }
837
838        for (int i=0; i<toShow.size(); i++) {
839            View v = toShow.get(i);
840            if (v.getParent() == null) {
841                mPile.addView(v, 0); // the notification shade has newest at the top
842            }
843        }
844    }
845
846    private void reloadAllNotificationIcons() {
847        if (mNotificationIcons == null) return;
848        mNotificationIcons.removeAllViews();
849        updateNotificationIcons();
850    }
851
852    private void updateNotificationIcons() {
853        loadNotificationShade();
854
855        final LinearLayout.LayoutParams params
856            = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
857
858        int N = mNotificationData.size();
859
860        if (DEBUG) {
861            Slog.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + mNotificationIcons);
862        }
863
864        ArrayList<View> toShow = new ArrayList<View>();
865
866        for (int i=0; i<N; i++) {
867            toShow.add(mNotificationData.get(N-i-1).icon);
868        }
869
870        ArrayList<View> toRemove = new ArrayList<View>();
871        for (int i=0; i<mNotificationIcons.getChildCount(); i++) {
872            View child = mNotificationIcons.getChildAt(i);
873            if (!toShow.contains(child)) {
874                toRemove.add(child);
875            }
876        }
877
878        for (View remove : toRemove) {
879            mNotificationIcons.removeView(remove);
880        }
881
882        for (int i=0; i<toShow.size(); i++) {
883            View v = toShow.get(i);
884            if (v.getParent() == null) {
885                mNotificationIcons.addView(v, i, params);
886            }
887        }
888    }
889
890    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
891        StatusBarNotification sbn = entry.notification;
892        RemoteViews remoteViews = sbn.notification.contentView;
893        if (remoteViews == null) {
894            return false;
895        }
896
897        // create the row view
898        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
899                Context.LAYOUT_INFLATER_SERVICE);
900        View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
901        View vetoButton = updateNotificationVetoButton(row, sbn);
902        vetoButton.setContentDescription(mContext.getString(
903                R.string.accessibility_remove_notification));
904
905        // the large icon
906        ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
907        if (sbn.notification.largeIcon != null) {
908            largeIcon.setImageBitmap(sbn.notification.largeIcon);
909            largeIcon.setContentDescription(sbn.notification.tickerText);
910        } else {
911            largeIcon.getLayoutParams().width = 0;
912            largeIcon.setVisibility(View.INVISIBLE);
913        }
914        largeIcon.setContentDescription(sbn.notification.tickerText);
915
916        // bind the click event to the content area
917        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
918        // XXX: update to allow controls within notification views
919        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
920//        content.setOnFocusChangeListener(mFocusChangeListener);
921        PendingIntent contentIntent = sbn.notification.contentIntent;
922        if (contentIntent != null) {
923            final View.OnClickListener listener = new NotificationClicker(contentIntent,
924                    sbn.pkg, sbn.tag, sbn.id);
925            largeIcon.setOnClickListener(listener);
926            content.setOnClickListener(listener);
927        } else {
928            largeIcon.setOnClickListener(null);
929            content.setOnClickListener(null);
930        }
931
932        View expanded = null;
933        Exception exception = null;
934        try {
935            expanded = remoteViews.apply(mContext, content);
936        }
937        catch (RuntimeException e) {
938            exception = e;
939        }
940        if (expanded == null) {
941            final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
942            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
943            return false;
944        } else {
945            content.addView(expanded);
946            row.setDrawingCacheEnabled(true);
947        }
948
949        entry.row = row;
950        entry.content = content;
951        entry.expanded = expanded;
952        entry.largeIcon = largeIcon;
953
954        return true;
955    }
956
957    StatusBarNotification removeNotificationViews(IBinder key) {
958        NotificationData.Entry entry = mNotificationData.remove(key);
959        if (entry == null) {
960            Slog.w(TAG, "removeNotification for unknown key: " + key);
961            return null;
962        }
963        // Remove the expanded view.
964        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
965        if (rowParent != null) rowParent.removeView(entry.row);
966        updateNotificationIcons();
967
968        return entry.notification;
969    }
970
971    private void setAreThereNotifications() {
972        final boolean any = mNotificationData.size() > 0;
973
974        final boolean clearable = any && mNotificationData.hasClearableItems();
975
976        if (DEBUG) {
977            Slog.d(TAG, "setAreThereNotifications: N=" + mNotificationData.size()
978                    + " any=" + any + " clearable=" + clearable);
979        }
980
981        if (mClearButton.isShown()) {
982            if (clearable != (mClearButton.getAlpha() == 1.0f)) {
983                ObjectAnimator.ofFloat(mClearButton, "alpha",
984                        clearable ? 1.0f : 0.0f)
985                    .setDuration(250)
986                    .start();
987            }
988        } else {
989            mClearButton.setAlpha(clearable ? 1.0f : 0.0f);
990        }
991        mClearButton.setEnabled(clearable);
992
993        /*
994        if (mNoNotificationsTitle.isShown()) {
995            if (any != (mNoNotificationsTitle.getAlpha() == 0.0f)) {
996                ObjectAnimator a = ObjectAnimator.ofFloat(mNoNotificationsTitle, "alpha",
997                            (any ? 0.0f : 0.75f));
998                a.setDuration(any ? 0 : 500);
999                a.setStartDelay(any ? 250 : 1000);
1000                a.start();
1001            }
1002        } else {
1003            mNoNotificationsTitle.setAlpha(any ? 0.0f : 0.75f);
1004        }
1005        */
1006    }
1007
1008    public void showClock(boolean show) {
1009        View clock = mStatusBarView.findViewById(R.id.clock);
1010        if (clock != null) {
1011            clock.setVisibility(show ? View.VISIBLE : View.GONE);
1012        }
1013    }
1014
1015    /**
1016     * State is one or more of the DISABLE constants from StatusBarManager.
1017     */
1018    public void disable(int state) {
1019        final int old = mDisabled;
1020        final int diff = state ^ old;
1021        mDisabled = state;
1022
1023        if (DEBUG) {
1024            Slog.d(TAG, String.format("disable: 0x%08x -> 0x%08x (diff: 0x%08x)",
1025                old, state, diff));
1026        }
1027
1028        StringBuilder flagdbg = new StringBuilder();
1029        flagdbg.append("disable: < ");
1030        flagdbg.append(((state & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand");
1031        flagdbg.append(((diff  & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " ");
1032        flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons");
1033        flagdbg.append(((diff  & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " ");
1034        flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts");
1035        flagdbg.append(((diff  & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " ");
1036        flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "TICKER" : "ticker");
1037        flagdbg.append(((diff  & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "* " : " ");
1038        flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info");
1039        flagdbg.append(((diff  & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " ");
1040        flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back");
1041        flagdbg.append(((diff  & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " ");
1042        flagdbg.append(((state & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home");
1043        flagdbg.append(((diff  & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " ");
1044        flagdbg.append(((state & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent");
1045        flagdbg.append(((diff  & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " ");
1046        flagdbg.append(((state & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock");
1047        flagdbg.append(((diff  & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " ");
1048        flagdbg.append(">");
1049        Slog.d(TAG, flagdbg.toString());
1050
1051        if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
1052            boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
1053            showClock(show);
1054        }
1055        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
1056            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
1057                animateCollapse();
1058            }
1059        }
1060
1061        if ((diff & (StatusBarManager.DISABLE_HOME
1062                        | StatusBarManager.DISABLE_RECENT
1063                        | StatusBarManager.DISABLE_BACK)) != 0) {
1064            // the nav bar will take care of these
1065            if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state);
1066
1067            if ((state & StatusBarManager.DISABLE_RECENT) != 0) {
1068                // close recents if it's visible
1069                mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
1070                mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
1071            }
1072        }
1073
1074        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
1075            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
1076                if (mTicking) {
1077                    mTicker.halt();
1078                } else {
1079                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
1080                }
1081            } else {
1082                if (!mExpandedVisible) {
1083                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
1084                }
1085            }
1086        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
1087            if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
1088                mTicker.halt();
1089            }
1090        }
1091    }
1092
1093    /**
1094     * All changes to the status bar and notifications funnel through here and are batched.
1095     */
1096    private class H extends Handler {
1097        public void handleMessage(Message m) {
1098            switch (m.what) {
1099                case MSG_ANIMATE:
1100                    doAnimation();
1101                    break;
1102                case MSG_ANIMATE_REVEAL:
1103                    doRevealAnimation();
1104                    break;
1105                case MSG_OPEN_NOTIFICATION_PANEL:
1106                    animateExpand();
1107                    break;
1108                case MSG_CLOSE_NOTIFICATION_PANEL:
1109                    animateCollapse();
1110                    break;
1111                case MSG_SHOW_INTRUDER:
1112                    setIntruderAlertVisibility(true);
1113                    break;
1114                case MSG_HIDE_INTRUDER:
1115                    setIntruderAlertVisibility(false);
1116                    break;
1117                case MSG_OPEN_RECENTS_PANEL:
1118                    if (DEBUG) Slog.d(TAG, "opening recents panel");
1119                    if (mRecentsPanel != null) {
1120                        mRecentsPanel.show(true, true);
1121                    }
1122                    break;
1123                case MSG_CLOSE_RECENTS_PANEL:
1124                    if (DEBUG) Slog.d(TAG, "closing recents panel");
1125                    if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
1126                        mRecentsPanel.show(false, true);
1127                    }
1128                    break;
1129            }
1130        }
1131    }
1132
1133    View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() {
1134        public void onFocusChange(View v, boolean hasFocus) {
1135            // Because 'v' is a ViewGroup, all its children will be (un)selected
1136            // too, which allows marqueeing to work.
1137            v.setSelected(hasFocus);
1138        }
1139    };
1140
1141    private void makeExpandedVisible() {
1142        if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
1143        if (mExpandedVisible) {
1144            return;
1145        }
1146        mExpandedVisible = true;
1147        visibilityChanged(true);
1148
1149        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1150        mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1151        mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1152        if (DEBUG) {
1153            Slog.d(TAG, "makeExpandedVisible: expanded params = " + mExpandedParams);
1154        }
1155        mExpandedDialog.getWindow().setAttributes(mExpandedParams);
1156        mExpandedView.requestFocus(View.FOCUS_FORWARD);
1157        mTrackingView.setVisibility(View.VISIBLE);
1158    }
1159
1160    public void animateExpand() {
1161        if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded);
1162        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
1163            return ;
1164        }
1165        if (mExpanded) {
1166            return;
1167        }
1168
1169        prepareTracking(0, true);
1170        performFling(0, mSelfExpandVelocityPx, true);
1171    }
1172
1173    public void animateCollapse() {
1174        animateCollapse(false);
1175    }
1176
1177    public void animateCollapse(boolean excludeRecents) {
1178        animateCollapse(excludeRecents, 1.0f);
1179    }
1180
1181    public void animateCollapse(boolean excludeRecents, float velocityMultiplier) {
1182        if (SPEW) {
1183            Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded
1184                    + " mExpandedVisible=" + mExpandedVisible
1185                    + " mExpanded=" + mExpanded
1186                    + " mAnimating=" + mAnimating
1187                    + " mAnimY=" + mAnimY
1188                    + " mAnimVel=" + mAnimVel);
1189        }
1190
1191        if (!excludeRecents) {
1192            mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
1193            mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
1194        }
1195
1196        if (!mExpandedVisible) {
1197            return;
1198        }
1199
1200        int y;
1201        if (mAnimating) {
1202            y = (int)mAnimY;
1203        } else {
1204            y = mDisplayMetrics.heightPixels-1;
1205        }
1206        // Let the fling think that we're open so it goes in the right direction
1207        // and doesn't try to re-open the windowshade.
1208        mExpanded = true;
1209        prepareTracking(y, false);
1210        performFling(y, -mSelfCollapseVelocityPx*velocityMultiplier, true);
1211    }
1212
1213    void performExpand() {
1214        if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded);
1215        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
1216            return ;
1217        }
1218        if (mExpanded) {
1219            return;
1220        }
1221
1222        mExpanded = true;
1223        makeExpandedVisible();
1224        updateExpandedViewPos(EXPANDED_FULL_OPEN);
1225
1226        if (false) postStartTracing();
1227    }
1228
1229    void performCollapse() {
1230        if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded
1231                + " mExpandedVisible=" + mExpandedVisible);
1232
1233        if (!mExpandedVisible) {
1234            return;
1235        }
1236        mExpandedVisible = false;
1237        visibilityChanged(false);
1238        mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1239        mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1240        mExpandedDialog.getWindow().setAttributes(mExpandedParams);
1241        mTrackingView.setVisibility(View.GONE);
1242
1243        if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) {
1244            setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
1245        }
1246
1247        if (!mExpanded) {
1248            return;
1249        }
1250        mExpanded = false;
1251        if (mPostCollapseCleanup != null) {
1252            mPostCollapseCleanup.run();
1253            mPostCollapseCleanup = null;
1254        }
1255    }
1256
1257    void doAnimation() {
1258        if (mAnimating) {
1259            if (SPEW) Slog.d(TAG, "doAnimation");
1260            if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY);
1261            incrementAnim();
1262            if (SPEW) Slog.d(TAG, "doAnimation after  mAnimY=" + mAnimY);
1263            if (mAnimY >= mDisplayMetrics.heightPixels-1) {
1264                if (SPEW) Slog.d(TAG, "Animation completed to expanded state.");
1265                mAnimating = false;
1266                updateExpandedViewPos(EXPANDED_FULL_OPEN);
1267                performExpand();
1268            }
1269            else if (mAnimY < mStatusBarView.getHeight()) {
1270                if (SPEW) Slog.d(TAG, "Animation completed to collapsed state.");
1271                mAnimating = false;
1272                updateExpandedViewPos(0);
1273                performCollapse();
1274            }
1275            else {
1276                updateExpandedViewPos((int)mAnimY);
1277                mCurAnimationTime += ANIM_FRAME_DURATION;
1278                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime);
1279            }
1280        }
1281    }
1282
1283    void stopTracking() {
1284        mTracking = false;
1285        mVelocityTracker.recycle();
1286        mVelocityTracker = null;
1287    }
1288
1289    void incrementAnim() {
1290        long now = SystemClock.uptimeMillis();
1291        float t = ((float)(now - mAnimLastTime)) / 1000;            // ms -> s
1292        final float y = mAnimY;
1293        final float v = mAnimVel;                                   // px/s
1294        final float a = mAnimAccel;                                 // px/s/s
1295        mAnimY = y + (v*t) + (0.5f*a*t*t);                          // px
1296        mAnimVel = v + (a*t);                                       // px/s
1297        mAnimLastTime = now;                                        // ms
1298        //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY
1299        //        + " mAnimAccel=" + mAnimAccel);
1300    }
1301
1302    void doRevealAnimation() {
1303        final int h = mCloseView.getHeight() + mStatusBarView.getHeight();
1304        if (mAnimatingReveal && mAnimating && mAnimY < h) {
1305            incrementAnim();
1306            if (mAnimY >= h) {
1307                mAnimY = h;
1308                updateExpandedViewPos((int)mAnimY);
1309            } else {
1310                updateExpandedViewPos((int)mAnimY);
1311                mCurAnimationTime += ANIM_FRAME_DURATION;
1312                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL),
1313                        mCurAnimationTime);
1314            }
1315        }
1316    }
1317
1318    void prepareTracking(int y, boolean opening) {
1319        if (CHATTY) {
1320            Slog.d(TAG, "panel: beginning to track the user's touch, y=" + y + " opening=" + opening);
1321        }
1322
1323        // there are some race conditions that cause this to be inaccurate; let's recalculate it any
1324        // time we're about to drag the panel
1325        updateExpandedSize();
1326
1327        mTracking = true;
1328        mVelocityTracker = VelocityTracker.obtain();
1329        if (opening) {
1330            mAnimAccel = mExpandAccelPx;
1331            mAnimVel = mFlingExpandMinVelocityPx;
1332            mAnimY = mStatusBarView.getHeight();
1333            updateExpandedViewPos((int)mAnimY);
1334            mAnimating = true;
1335            mAnimatingReveal = true;
1336            mHandler.removeMessages(MSG_ANIMATE);
1337            mHandler.removeMessages(MSG_ANIMATE_REVEAL);
1338            long now = SystemClock.uptimeMillis();
1339            mAnimLastTime = now;
1340            mCurAnimationTime = now + ANIM_FRAME_DURATION;
1341            mAnimating = true;
1342            mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL),
1343                    mCurAnimationTime);
1344            makeExpandedVisible();
1345        } else {
1346            // it's open, close it?
1347            if (mAnimating) {
1348                mAnimating = false;
1349                mHandler.removeMessages(MSG_ANIMATE);
1350            }
1351            updateExpandedViewPos(y + mViewDelta);
1352        }
1353    }
1354
1355    void performFling(int y, float vel, boolean always) {
1356        if (CHATTY) {
1357            Slog.d(TAG, "panel: will fling, y=" + y + " vel=" + vel);
1358        }
1359
1360        mAnimatingReveal = false;
1361
1362        mAnimY = y;
1363        mAnimVel = vel;
1364
1365        //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel);
1366
1367        if (mExpanded) {
1368            if (!always && (
1369                    vel > mFlingCollapseMinVelocityPx
1370                    || (y > (mDisplayMetrics.heightPixels*(1f-mCollapseMinDisplayFraction)) &&
1371                        vel > -mFlingExpandMinVelocityPx))) {
1372                // We are expanded, but they didn't move sufficiently to cause
1373                // us to retract.  Animate back to the expanded position.
1374                mAnimAccel = mExpandAccelPx;
1375                if (vel < 0) {
1376                    mAnimVel = 0;
1377                }
1378            }
1379            else {
1380                // We are expanded and are now going to animate away.
1381                mAnimAccel = -mCollapseAccelPx;
1382                if (vel > 0) {
1383                    mAnimVel = 0;
1384                }
1385            }
1386        } else {
1387            if (always || (
1388                    vel > mFlingExpandMinVelocityPx
1389                    || (y > (mDisplayMetrics.heightPixels*(1f-mExpandMinDisplayFraction)) &&
1390                        vel > -mFlingCollapseMinVelocityPx))) {
1391                // We are collapsed, and they moved enough to allow us to
1392                // expand.  Animate in the notifications.
1393                mAnimAccel = mExpandAccelPx;
1394                if (vel < 0) {
1395                    mAnimVel = 0;
1396                }
1397            }
1398            else {
1399                // We are collapsed, but they didn't move sufficiently to cause
1400                // us to retract.  Animate back to the collapsed position.
1401                mAnimAccel = -mCollapseAccelPx;
1402                if (vel > 0) {
1403                    mAnimVel = 0;
1404                }
1405            }
1406        }
1407        //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel
1408        //        + " mAnimAccel=" + mAnimAccel);
1409
1410        long now = SystemClock.uptimeMillis();
1411        mAnimLastTime = now;
1412        mCurAnimationTime = now + ANIM_FRAME_DURATION;
1413        mAnimating = true;
1414        mHandler.removeMessages(MSG_ANIMATE);
1415        mHandler.removeMessages(MSG_ANIMATE_REVEAL);
1416        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime);
1417        stopTracking();
1418    }
1419
1420    boolean interceptTouchEvent(MotionEvent event) {
1421        if (SPEW) {
1422            Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled="
1423                + mDisabled);
1424        } else if (CHATTY) {
1425            if (event.getAction() != MotionEvent.ACTION_MOVE) {
1426                Slog.d(TAG, String.format(
1427                            "panel: %s at (%f, %f) mDisabled=0x%08x",
1428                            MotionEvent.actionToString(event.getAction()),
1429                            event.getRawX(), event.getRawY(), mDisabled));
1430            }
1431        }
1432
1433        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
1434            return false;
1435        }
1436
1437        final int action = event.getAction();
1438        final int statusBarSize = mStatusBarView.getHeight();
1439        final int hitSize = statusBarSize*2;
1440        final int y = (int)event.getRawY();
1441        if (action == MotionEvent.ACTION_DOWN) {
1442            if (!mExpanded) {
1443                mViewDelta = statusBarSize - y;
1444            } else {
1445                mTrackingView.getLocationOnScreen(mAbsPos);
1446                mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y;
1447            }
1448            if ((!mExpanded && y < hitSize) ||
1449                    (mExpanded && y > (mDisplayMetrics.heightPixels-hitSize))) {
1450
1451                // We drop events at the edge of the screen to make the windowshade come
1452                // down by accident less, especially when pushing open a device with a keyboard
1453                // that rotates (like g1 and droid)
1454                int x = (int)event.getRawX();
1455                final int edgeBorder = mEdgeBorder;
1456                if (x >= edgeBorder && x < mDisplayMetrics.widthPixels - edgeBorder) {
1457                    prepareTracking(y, !mExpanded);// opening if we're not already fully visible
1458                    trackMovement(event);
1459                }
1460            }
1461        } else if (mTracking) {
1462            trackMovement(event);
1463            final int minY = statusBarSize + mCloseView.getHeight();
1464            if (action == MotionEvent.ACTION_MOVE) {
1465                if (mAnimatingReveal && y < minY) {
1466                    // nothing
1467                } else  {
1468                    mAnimatingReveal = false;
1469                    updateExpandedViewPos(y + mViewDelta);
1470                }
1471            } else if (action == MotionEvent.ACTION_UP
1472                    || action == MotionEvent.ACTION_CANCEL) {
1473                mVelocityTracker.computeCurrentVelocity(1000);
1474
1475                float yVel = mVelocityTracker.getYVelocity();
1476                boolean negative = yVel < 0;
1477
1478                float xVel = mVelocityTracker.getXVelocity();
1479                if (xVel < 0) {
1480                    xVel = -xVel;
1481                }
1482                if (xVel > mFlingGestureMaxXVelocityPx) {
1483                    xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
1484                }
1485
1486                float vel = (float)Math.hypot(yVel, xVel);
1487                if (negative) {
1488                    vel = -vel;
1489                }
1490
1491                if (CHATTY) {
1492                    Slog.d(TAG, String.format("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f",
1493                        mVelocityTracker.getXVelocity(),
1494                        mVelocityTracker.getYVelocity(),
1495                        xVel, yVel,
1496                        vel));
1497                }
1498
1499                performFling(y + mViewDelta, vel, false);
1500            }
1501
1502        }
1503        return false;
1504    }
1505
1506    private void trackMovement(MotionEvent event) {
1507        // Add movement to velocity tracker using raw screen X and Y coordinates instead
1508        // of window coordinates because the window frame may be moving at the same time.
1509        float deltaX = event.getRawX() - event.getX();
1510        float deltaY = event.getRawY() - event.getY();
1511        event.offsetLocation(deltaX, deltaY);
1512        mVelocityTracker.addMovement(event);
1513        event.offsetLocation(-deltaX, -deltaY);
1514    }
1515
1516    @Override // CommandQueue
1517    public void setSystemUiVisibility(int vis) {
1518        final int old = mSystemUiVisibility;
1519        final int diff = vis ^ old;
1520
1521        if (diff != 0) {
1522            mSystemUiVisibility = vis;
1523
1524            if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) {
1525                final boolean lightsOut = (0 != (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE));
1526                if (lightsOut) {
1527                    animateCollapse();
1528                }
1529                if (mNavigationBarView != null) {
1530                    mNavigationBarView.setLowProfile(lightsOut);
1531                }
1532            }
1533
1534            notifyUiVisibilityChanged();
1535        }
1536    }
1537
1538    public void setLightsOn(boolean on) {
1539        Log.v(TAG, "setLightsOn(" + on + ")");
1540        if (on) {
1541            setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE);
1542        } else {
1543            setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE);
1544        }
1545    }
1546
1547    private void notifyUiVisibilityChanged() {
1548        try {
1549            mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility);
1550        } catch (RemoteException ex) {
1551        }
1552    }
1553
1554    public void topAppWindowChanged(boolean showMenu) {
1555        if (DEBUG) {
1556            Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button");
1557        }
1558        if (mNavigationBarView != null) {
1559            mNavigationBarView.setMenuVisibility(showMenu);
1560        }
1561
1562        // See above re: lights-out policy for legacy apps.
1563        if (showMenu) setLightsOn(true);
1564    }
1565
1566    // Not supported
1567    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { }
1568    @Override
1569    public void setHardKeyboardStatus(boolean available, boolean enabled) { }
1570
1571    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
1572        return new NotificationClicker(intent, pkg, tag, id);
1573    }
1574
1575    private class NotificationClicker implements View.OnClickListener {
1576        private PendingIntent mIntent;
1577        private String mPkg;
1578        private String mTag;
1579        private int mId;
1580
1581        NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
1582            mIntent = intent;
1583            mPkg = pkg;
1584            mTag = tag;
1585            mId = id;
1586        }
1587
1588        public void onClick(View v) {
1589            try {
1590                // The intent we are sending is for the application, which
1591                // won't have permission to immediately start an activity after
1592                // the user switches to home.  We know it is safe to do at this
1593                // point, so make sure new activity switches are now allowed.
1594                ActivityManagerNative.getDefault().resumeAppSwitches();
1595                // Also, notifications can be launched from the lock screen,
1596                // so dismiss the lock screen when the activity starts.
1597                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
1598            } catch (RemoteException e) {
1599            }
1600
1601            if (mIntent != null) {
1602                int[] pos = new int[2];
1603                v.getLocationOnScreen(pos);
1604                Intent overlay = new Intent();
1605                overlay.setSourceBounds(
1606                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
1607                try {
1608                    mIntent.send(mContext, 0, overlay);
1609                } catch (PendingIntent.CanceledException e) {
1610                    // the stack trace isn't very helpful here.  Just log the exception message.
1611                    Slog.w(TAG, "Sending contentIntent failed: " + e);
1612                }
1613
1614                KeyguardManager kgm =
1615                    (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
1616                if (kgm != null) kgm.exitKeyguardSecurely(null);
1617            }
1618
1619            try {
1620                mBarService.onNotificationClick(mPkg, mTag, mId);
1621            } catch (RemoteException ex) {
1622                // system process is dead if we're here.
1623            }
1624
1625            // close the shade if it was open
1626            animateCollapse();
1627
1628            // If this click was on the intruder alert, hide that instead
1629            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
1630        }
1631    }
1632
1633    private void tick(StatusBarNotification n) {
1634        // Show the ticker if one is requested. Also don't do this
1635        // until status bar window is attached to the window manager,
1636        // because...  well, what's the point otherwise?  And trying to
1637        // run a ticker without being attached will crash!
1638        if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) {
1639            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
1640                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
1641                mTicker.addEntry(n);
1642            }
1643        }
1644    }
1645
1646    /**
1647     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
1648     * about the failure.
1649     *
1650     * WARNING: this will call back into us.  Don't hold any locks.
1651     */
1652    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
1653        removeNotification(key);
1654        try {
1655            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
1656        } catch (RemoteException ex) {
1657            // The end is nigh.
1658        }
1659    }
1660
1661    private class MyTicker extends Ticker {
1662        MyTicker(Context context, View sb) {
1663            super(context, sb);
1664        }
1665
1666        @Override
1667        public void tickerStarting() {
1668            mTicking = true;
1669            mIcons.setVisibility(View.GONE);
1670            mTickerView.setVisibility(View.VISIBLE);
1671            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null));
1672            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null));
1673        }
1674
1675        @Override
1676        public void tickerDone() {
1677            mIcons.setVisibility(View.VISIBLE);
1678            mTickerView.setVisibility(View.GONE);
1679            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
1680            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out,
1681                        mTickingDoneListener));
1682        }
1683
1684        public void tickerHalting() {
1685            mIcons.setVisibility(View.VISIBLE);
1686            mTickerView.setVisibility(View.GONE);
1687            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
1688            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out,
1689                        mTickingDoneListener));
1690        }
1691    }
1692
1693    Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {;
1694        public void onAnimationEnd(Animation animation) {
1695            mTicking = false;
1696        }
1697        public void onAnimationRepeat(Animation animation) {
1698        }
1699        public void onAnimationStart(Animation animation) {
1700        }
1701    };
1702
1703    private Animation loadAnim(int id, Animation.AnimationListener listener) {
1704        Animation anim = AnimationUtils.loadAnimation(mContext, id);
1705        if (listener != null) {
1706            anim.setAnimationListener(listener);
1707        }
1708        return anim;
1709    }
1710
1711    public String viewInfo(View v) {
1712        return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom()
1713                + " " + v.getWidth() + "x" + v.getHeight() + ")";
1714    }
1715
1716    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1717        synchronized (mQueueLock) {
1718            pw.println("Current Status Bar state:");
1719            pw.println("  mExpanded=" + mExpanded
1720                    + ", mExpandedVisible=" + mExpandedVisible);
1721            pw.println("  mTicking=" + mTicking);
1722            pw.println("  mTracking=" + mTracking);
1723            pw.println("  mAnimating=" + mAnimating
1724                    + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel
1725                    + ", mAnimAccel=" + mAnimAccel);
1726            pw.println("  mCurAnimationTime=" + mCurAnimationTime
1727                    + " mAnimLastTime=" + mAnimLastTime);
1728            pw.println("  mAnimatingReveal=" + mAnimatingReveal
1729                    + " mViewDelta=" + mViewDelta);
1730            pw.println("  mDisplayMetrics=" + mDisplayMetrics);
1731            pw.println("  mExpandedParams: " + mExpandedParams);
1732            pw.println("  mExpandedView: " + viewInfo(mExpandedView));
1733            pw.println("  mExpandedDialog: " + mExpandedDialog);
1734            pw.println("  mTrackingParams: " + mTrackingParams);
1735            pw.println("  mTrackingView: " + viewInfo(mTrackingView));
1736            pw.println("  mPile: " + viewInfo(mPile));
1737            pw.println("  mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle));
1738            pw.println("  mCloseView: " + viewInfo(mCloseView));
1739            pw.println("  mTickerView: " + viewInfo(mTickerView));
1740            pw.println("  mScrollView: " + viewInfo(mScrollView)
1741                    + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY());
1742        }
1743
1744        if (DUMPTRUCK) {
1745            synchronized (mNotificationData) {
1746                int N = mNotificationData.size();
1747                pw.println("  notification icons: " + N);
1748                for (int i=0; i<N; i++) {
1749                    NotificationData.Entry e = mNotificationData.get(i);
1750                    pw.println("    [" + i + "] key=" + e.key + " icon=" + e.icon);
1751                    StatusBarNotification n = e.notification;
1752                    pw.println("         pkg=" + n.pkg + " id=" + n.id + " priority=" + n.priority);
1753                    pw.println("         notification=" + n.notification);
1754                    pw.println("         tickerText=\"" + n.notification.tickerText + "\"");
1755                }
1756            }
1757
1758            int N = mStatusIcons.getChildCount();
1759            pw.println("  system icons: " + N);
1760            for (int i=0; i<N; i++) {
1761                StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i);
1762                pw.println("    [" + i + "] icon=" + ic);
1763            }
1764
1765            if (false) {
1766                pw.println("see the logcat for a dump of the views we have created.");
1767                // must happen on ui thread
1768                mHandler.post(new Runnable() {
1769                        public void run() {
1770                            mStatusBarView.getLocationOnScreen(mAbsPos);
1771                            Slog.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
1772                                    + ") " + mStatusBarView.getWidth() + "x"
1773                                    + mStatusBarView.getHeight());
1774                            mStatusBarView.debug();
1775
1776                            mExpandedView.getLocationOnScreen(mAbsPos);
1777                            Slog.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
1778                                    + ") " + mExpandedView.getWidth() + "x"
1779                                    + mExpandedView.getHeight());
1780                            mExpandedView.debug();
1781
1782                            mTrackingView.getLocationOnScreen(mAbsPos);
1783                            Slog.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
1784                                    + ") " + mTrackingView.getWidth() + "x"
1785                                    + mTrackingView.getHeight());
1786                            mTrackingView.debug();
1787                        }
1788                    });
1789            }
1790        }
1791
1792        mNetworkController.dump(fd, pw, args);
1793    }
1794
1795    void onBarViewAttached() {
1796        WindowManager.LayoutParams lp;
1797        int pixelFormat;
1798        Drawable bg;
1799
1800        /// ---------- Tracking View --------------
1801        bg = mTrackingView.getBackground();
1802        if (bg != null) {
1803            pixelFormat = bg.getOpacity();
1804        }
1805
1806        lp = new WindowManager.LayoutParams(
1807                ViewGroup.LayoutParams.MATCH_PARENT,
1808                ViewGroup.LayoutParams.MATCH_PARENT,
1809                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
1810                0
1811                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1812                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
1813                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
1814                PixelFormat.TRANSLUCENT);
1815        if (ActivityManager.isHighEndGfx(mDisplay)) {
1816            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
1817        }
1818//        lp.token = mStatusBarView.getWindowToken();
1819        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
1820        lp.setTitle("TrackingView");
1821        lp.y = mTrackingPosition;
1822        mTrackingParams = lp;
1823
1824        WindowManagerImpl.getDefault().addView(mTrackingView, lp);
1825    }
1826
1827    void onTrackingViewAttached() {
1828        WindowManager.LayoutParams lp;
1829        int pixelFormat;
1830
1831        /// ---------- Expanded View --------------
1832        pixelFormat = PixelFormat.TRANSLUCENT;
1833
1834        lp = mExpandedDialog.getWindow().getAttributes();
1835        lp.x = 0;
1836        mTrackingPosition = lp.y = mDisplayMetrics.heightPixels; // sufficiently large negative
1837        lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
1838        lp.flags = 0
1839                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1840                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
1841                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1842                | WindowManager.LayoutParams.FLAG_DITHER
1843                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1844        if (ActivityManager.isHighEndGfx(mDisplay)) {
1845            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
1846        }
1847        lp.format = pixelFormat;
1848        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
1849        lp.setTitle("StatusBarExpanded");
1850        mExpandedParams = lp;
1851        updateExpandedSize();
1852        mExpandedDialog.getWindow().setFormat(pixelFormat);
1853
1854        mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
1855        mExpandedDialog.setContentView(mExpandedView,
1856                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1857                                           ViewGroup.LayoutParams.MATCH_PARENT));
1858        mExpandedDialog.getWindow().setBackgroundDrawable(null);
1859        mExpandedDialog.show();
1860    }
1861
1862    void setNotificationIconVisibility(boolean visible, int anim) {
1863        int old = mNotificationIcons.getVisibility();
1864        int v = visible ? View.VISIBLE : View.INVISIBLE;
1865        if (old != v) {
1866            mNotificationIcons.setVisibility(v);
1867            mNotificationIcons.startAnimation(loadAnim(anim, null));
1868        }
1869    }
1870
1871    void updateExpandedInvisiblePosition() {
1872        if (mTrackingView != null) {
1873            mTrackingPosition = -mDisplayMetrics.heightPixels;
1874            if (mTrackingParams != null) {
1875                mTrackingParams.y = mTrackingPosition;
1876                WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
1877            }
1878        }
1879        if (mExpandedParams != null) {
1880            mExpandedParams.y = -mDisplayMetrics.heightPixels;
1881            mExpandedDialog.getWindow().setAttributes(mExpandedParams);
1882        }
1883    }
1884
1885    void updateExpandedViewPos(int expandedPosition) {
1886        if (SPEW) {
1887            Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition
1888                    + " mTrackingParams.y=" + ((mTrackingParams == null) ? "?" : mTrackingParams.y)
1889                    + " mTrackingPosition=" + mTrackingPosition);
1890        }
1891
1892        int h = mStatusBarView.getHeight();
1893        int disph = mDisplayMetrics.heightPixels;
1894
1895        // If the expanded view is not visible, make sure they're still off screen.
1896        // Maybe the view was resized.
1897        if (!mExpandedVisible) {
1898            updateExpandedInvisiblePosition();
1899            return;
1900        }
1901
1902        // tracking view...
1903        int pos;
1904        if (expandedPosition == EXPANDED_FULL_OPEN) {
1905            pos = h;
1906        }
1907        else if (expandedPosition == EXPANDED_LEAVE_ALONE) {
1908            pos = mTrackingPosition;
1909        }
1910        else {
1911            if (expandedPosition <= disph) {
1912                pos = expandedPosition;
1913            } else {
1914                pos = disph;
1915            }
1916            pos -= disph-h;
1917        }
1918        mTrackingPosition = mTrackingParams.y = pos;
1919        mTrackingParams.height = disph-h;
1920        WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
1921
1922        if (mExpandedParams != null) {
1923            if (mCloseView.getWindowVisibility() == View.VISIBLE) {
1924                mCloseView.getLocationInWindow(mPositionTmp);
1925                final int closePos = mPositionTmp[1];
1926
1927                mExpandedContents.getLocationInWindow(mPositionTmp);
1928                final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight();
1929
1930                mExpandedParams.y = pos + mTrackingView.getHeight()
1931                        - (mTrackingParams.height-closePos) - contentsBottom;
1932
1933                if (SPEW) {
1934                    Slog.d(PhoneStatusBar.TAG,
1935                            "pos=" + pos +
1936                            " trackingHeight=" + mTrackingView.getHeight() +
1937                            " (trackingParams.height - closePos)=" +
1938                                (mTrackingParams.height - closePos) +
1939                            " contentsBottom=" + contentsBottom);
1940                }
1941
1942            } else {
1943                // If the tracking view is not yet visible, then we can't have
1944                // a good value of the close view location.  We need to wait for
1945                // it to be visible to do a layout.
1946                mExpandedParams.y = -mDisplayMetrics.heightPixels;
1947            }
1948            int max = h;
1949            if (mExpandedParams.y > max) {
1950                mExpandedParams.y = max;
1951            }
1952            int min = mTrackingPosition;
1953            if (mExpandedParams.y < min) {
1954                mExpandedParams.y = min;
1955            }
1956
1957            boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h;
1958            if (!visible) {
1959                // if the contents aren't visible, move the expanded view way off screen
1960                // because the window itself extends below the content view.
1961                mExpandedParams.y = -disph;
1962            }
1963            mExpandedDialog.getWindow().setAttributes(mExpandedParams);
1964
1965            // As long as this isn't just a repositioning that's not supposed to affect
1966            // the user's perception of what's showing, call to say that the visibility
1967            // has changed. (Otherwise, someone else will call to do that).
1968            if (expandedPosition != EXPANDED_LEAVE_ALONE) {
1969                if (SPEW) Slog.d(TAG, "updateExpandedViewPos visibilityChanged(" + visible + ")");
1970                visibilityChanged(visible);
1971            }
1972        }
1973
1974        if (SPEW) {
1975            Slog.d(TAG, "updateExpandedViewPos after  expandedPosition=" + expandedPosition
1976                    + " mTrackingParams.y=" + mTrackingParams.y
1977                    + " mTrackingPosition=" + mTrackingPosition
1978                    + " mExpandedParams.y=" + mExpandedParams.y
1979                    + " mExpandedParams.height=" + mExpandedParams.height);
1980        }
1981    }
1982
1983    int getExpandedHeight(int disph) {
1984        if (DEBUG) {
1985            Slog.d(TAG, "getExpandedHeight(" + disph + "): sbView="
1986                    + mStatusBarView.getHeight() + " closeView=" + mCloseView.getHeight());
1987        }
1988        return disph - mStatusBarView.getHeight() - mCloseView.getHeight();
1989    }
1990
1991    void updateDisplaySize() {
1992        mDisplay.getMetrics(mDisplayMetrics);
1993        if (DEBUG) {
1994            Slog.d(TAG, "updateDisplaySize: " + mDisplayMetrics);
1995        }
1996        updateExpandedSize();
1997    }
1998
1999    void updateExpandedSize() {
2000        if (DEBUG) {
2001            Slog.d(TAG, "updateExpandedSize()");
2002        }
2003        if (mExpandedDialog != null && mExpandedParams != null && mDisplayMetrics != null) {
2004            mExpandedParams.width = mDisplayMetrics.widthPixels;
2005            mExpandedParams.height = getExpandedHeight(mDisplayMetrics.heightPixels);
2006            if (!mExpandedVisible) {
2007                updateExpandedInvisiblePosition();
2008            } else {
2009                mExpandedDialog.getWindow().setAttributes(mExpandedParams);
2010            }
2011            if (DEBUG) {
2012                Slog.d(TAG, "updateExpandedSize: height=" + mExpandedParams.height + " " +
2013                    (mExpandedVisible ? "VISIBLE":"INVISIBLE"));
2014            }
2015        }
2016    }
2017
2018    public void toggleRecentApps() {
2019        int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
2020                ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
2021        mHandler.removeMessages(msg);
2022        mHandler.sendEmptyMessage(msg);
2023    }
2024
2025    /**
2026     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
2027     * This was added last-minute and is inconsistent with the way the rest of the notifications
2028     * are handled, because the notification isn't really cancelled.  The lights are just
2029     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
2030     * this is what he wants. (see bug 1131461)
2031     */
2032    void visibilityChanged(boolean visible) {
2033        if (mPanelSlightlyVisible != visible) {
2034            mPanelSlightlyVisible = visible;
2035            try {
2036                mBarService.onPanelRevealed();
2037            } catch (RemoteException ex) {
2038                // Won't fail unless the world has ended.
2039            }
2040        }
2041    }
2042
2043    void performDisableActions(int net) {
2044        int old = mDisabled;
2045        int diff = net ^ old;
2046        mDisabled = net;
2047
2048        // act accordingly
2049        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
2050            if ((net & StatusBarManager.DISABLE_EXPAND) != 0) {
2051                Slog.d(TAG, "DISABLE_EXPAND: yes");
2052                animateCollapse();
2053            }
2054        }
2055        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
2056            if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
2057                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
2058                if (mTicking) {
2059                    mNotificationIcons.setVisibility(View.INVISIBLE);
2060                    mTicker.halt();
2061                } else {
2062                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
2063                }
2064            } else {
2065                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
2066                if (!mExpandedVisible) {
2067                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
2068                }
2069            }
2070        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
2071            if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
2072                mTicker.halt();
2073            }
2074        }
2075    }
2076
2077    private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
2078        final int mini(int a, int b) {
2079            return (b>a?a:b);
2080        }
2081        public void onClick(View v) {
2082            synchronized (mNotificationData) {
2083                // animate-swipe all dismissable notifications, then animate the shade closed
2084                int numChildren = mPile.getChildCount();
2085
2086                int scrollTop = mScrollView.getScrollY();
2087                int scrollBottom = scrollTop + mScrollView.getHeight();
2088                final ArrayList<View> snapshot = new ArrayList<View>(numChildren);
2089                for (int i=0; i<numChildren; i++) {
2090                    final View child = mPile.getChildAt(i);
2091                    if (mPile.canChildBeDismissed(child) && child.getBottom() > scrollTop &&
2092                            child.getTop() < scrollBottom) {
2093                        snapshot.add(child);
2094                    }
2095                }
2096                final int N = snapshot.size();
2097                new Thread(new Runnable() {
2098                    @Override
2099                    public void run() {
2100                        // Decrease the delay for every row we animate to give the sense of
2101                        // accelerating the swipes
2102                        final int ROW_DELAY_DECREMENT = 10;
2103                        int currentDelay = 140;
2104                        int totalDelay = 0;
2105
2106                        // Set the shade-animating state to avoid doing other work during
2107                        // all of these animations. In particular, avoid layout and
2108                        // redrawing when collapsing the shade.
2109                        mPile.setViewRemoval(false);
2110
2111                        mPostCollapseCleanup = new Runnable() {
2112                            public void run() {
2113                                try {
2114                                    mPile.setViewRemoval(true);
2115                                    mBarService.onClearAllNotifications();
2116                                } catch (Exception ex) { }
2117                            }
2118                        };
2119
2120                        View sampleView = snapshot.get(0);
2121                        int width = sampleView.getWidth();
2122                        final int velocity = (int)(width * 8); // 1000/8 = 125 ms duration
2123                        for (View v : snapshot) {
2124                            final View _v = v;
2125                            mHandler.postDelayed(new Runnable() {
2126                                @Override
2127                                public void run() {
2128                                    mPile.dismissRowAnimated(_v, velocity);
2129                                }
2130                            }, totalDelay);
2131                            currentDelay = Math.max(50, currentDelay - ROW_DELAY_DECREMENT);
2132                            totalDelay += currentDelay;
2133                        }
2134                        // Delay the collapse animation until after all swipe animations have
2135                        // finished. Provide some buffer because there may be some extra delay
2136                        // before actually starting each swipe animation. Ideally, we'd
2137                        // synchronize the end of those animations with the start of the collaps
2138                        // exactly.
2139                        mHandler.postDelayed(new Runnable() {
2140                            public void run() {
2141                                animateCollapse(false);
2142                            }
2143                        }, totalDelay + 225);
2144                    }
2145                }).start();
2146            }
2147        }
2148    };
2149
2150    private View.OnClickListener mSettingsButtonListener = new View.OnClickListener() {
2151        public void onClick(View v) {
2152            v.getContext().startActivity(new Intent(Settings.ACTION_SETTINGS)
2153                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
2154            animateCollapse();
2155        }
2156    };
2157
2158    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
2159        public void onReceive(Context context, Intent intent) {
2160            String action = intent.getAction();
2161            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
2162                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
2163                boolean excludeRecents = false;
2164                if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
2165                    String reason = intent.getStringExtra("reason");
2166                    if (reason != null) {
2167                        excludeRecents = reason.equals("recentapps");
2168                    }
2169                }
2170                animateCollapse(excludeRecents);
2171            }
2172            else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
2173                repositionNavigationBar();
2174                updateResources();
2175            }
2176        }
2177    };
2178
2179    private void setIntruderAlertVisibility(boolean vis) {
2180        mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE);
2181    }
2182
2183    /**
2184     * Reload some of our resources when the configuration changes.
2185     *
2186     * We don't reload everything when the configuration changes -- we probably
2187     * should, but getting that smooth is tough.  Someday we'll fix that.  In the
2188     * meantime, just update the things that we know change.
2189     */
2190    void updateResources() {
2191        final Context context = mContext;
2192        final Resources res = context.getResources();
2193
2194        if (mClearButton instanceof TextView) {
2195            ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button));
2196        }
2197        mNoNotificationsTitle.setText(context.getText(R.string.status_bar_no_notifications_title));
2198
2199        loadDimens();
2200    }
2201
2202    protected void loadDimens() {
2203        final Resources res = mContext.getResources();
2204
2205        mNaturalBarHeight = res.getDimensionPixelSize(
2206                com.android.internal.R.dimen.status_bar_height);
2207
2208        int newIconSize = res.getDimensionPixelSize(
2209            com.android.internal.R.dimen.status_bar_icon_size);
2210        int newIconHPadding = res.getDimensionPixelSize(
2211            R.dimen.status_bar_icon_padding);
2212
2213        if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) {
2214//            Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding);
2215            mIconHPadding = newIconHPadding;
2216            mIconSize = newIconSize;
2217            //reloadAllNotificationIcons(); // reload the tray
2218        }
2219
2220        mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
2221
2222        mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
2223        mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
2224        mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
2225        mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
2226
2227        mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
2228        mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
2229
2230        mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
2231        mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
2232
2233        mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
2234
2235        if (false) Slog.v(TAG, "updateResources");
2236    }
2237
2238    //
2239    // tracing
2240    //
2241
2242    void postStartTracing() {
2243        mHandler.postDelayed(mStartTracing, 3000);
2244    }
2245
2246    void vibrate() {
2247        android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService(
2248                Context.VIBRATOR_SERVICE);
2249        vib.vibrate(250);
2250    }
2251
2252    Runnable mStartTracing = new Runnable() {
2253        public void run() {
2254            vibrate();
2255            SystemClock.sleep(250);
2256            Slog.d(TAG, "startTracing");
2257            android.os.Debug.startMethodTracing("/data/statusbar-traces/trace");
2258            mHandler.postDelayed(mStopTracing, 10000);
2259        }
2260    };
2261
2262    Runnable mStopTracing = new Runnable() {
2263        public void run() {
2264            android.os.Debug.stopMethodTracing();
2265            Slog.d(TAG, "stopTracing");
2266            vibrate();
2267        }
2268    };
2269
2270    public class TouchOutsideListener implements View.OnTouchListener {
2271        private int mMsg;
2272        private RecentsPanelView mPanel;
2273
2274        public TouchOutsideListener(int msg, RecentsPanelView panel) {
2275            mMsg = msg;
2276            mPanel = panel;
2277        }
2278
2279        public boolean onTouch(View v, MotionEvent ev) {
2280            final int action = ev.getAction();
2281            if (action == MotionEvent.ACTION_OUTSIDE
2282                || (action == MotionEvent.ACTION_DOWN
2283                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
2284                mHandler.removeMessages(mMsg);
2285                mHandler.sendEmptyMessage(mMsg);
2286                return true;
2287            }
2288            return false;
2289        }
2290    }
2291}
2292
2293