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