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