TabletStatusBar.java revision 0424716328a7d0f7bb794d24f7481a76be08d379
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.statusbar.tablet;
18
19import java.io.FileDescriptor;
20import java.io.PrintWriter;
21import java.util.ArrayList;
22
23import android.animation.LayoutTransition;
24import android.animation.ObjectAnimator;
25import android.app.ActivityManagerNative;
26import android.app.KeyguardManager;
27import android.app.Notification;
28import android.app.PendingIntent;
29import android.app.StatusBarManager;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.SharedPreferences;
35import android.content.pm.ApplicationInfo;
36import android.content.pm.PackageManager.NameNotFoundException;
37import android.content.res.Configuration;
38import android.content.res.Resources;
39import android.graphics.PixelFormat;
40import android.graphics.Point;
41import android.graphics.Rect;
42import android.graphics.drawable.Drawable;
43import android.graphics.drawable.LayerDrawable;
44import android.inputmethodservice.InputMethodService;
45import android.os.Build;
46import android.os.IBinder;
47import android.os.Message;
48import android.os.RemoteException;
49import android.os.ServiceManager;
50import android.text.TextUtils;
51import android.util.Slog;
52import android.view.Display;
53import android.view.Gravity;
54import android.view.IWindowManager;
55import android.view.KeyEvent;
56import android.view.LayoutInflater;
57import android.view.MotionEvent;
58import android.view.SoundEffectConstants;
59import android.view.VelocityTracker;
60import android.view.View;
61import android.view.ViewConfiguration;
62import android.view.ViewGroup;
63import android.view.ViewGroup.LayoutParams;
64import android.view.WindowManager;
65import android.view.WindowManagerImpl;
66import android.view.accessibility.AccessibilityEvent;
67import android.widget.ImageView;
68import android.widget.LinearLayout;
69import android.widget.RemoteViews;
70import android.widget.ScrollView;
71import android.widget.TextView;
72
73import com.android.internal.statusbar.StatusBarIcon;
74import com.android.internal.statusbar.StatusBarNotification;
75import com.android.systemui.R;
76import com.android.systemui.recent.RecentTasksLoader;
77import com.android.systemui.recent.RecentsPanelView;
78import com.android.systemui.statusbar.BaseStatusBar;
79import com.android.systemui.statusbar.NotificationData;
80import com.android.systemui.statusbar.SignalClusterView;
81import com.android.systemui.statusbar.StatusBarIconView;
82import com.android.systemui.statusbar.policy.BatteryController;
83import com.android.systemui.statusbar.policy.BluetoothController;
84import com.android.systemui.statusbar.policy.CompatModeButton;
85import com.android.systemui.statusbar.policy.LocationController;
86import com.android.systemui.statusbar.policy.NetworkController;
87import com.android.systemui.statusbar.policy.Prefs;
88
89public class TabletStatusBar extends BaseStatusBar implements
90        HeightReceiver.OnBarHeightChangedListener,
91        InputMethodsPanel.OnHardKeyboardEnabledChangeListener,
92        RecentsPanelView.OnRecentsPanelVisibilityChangedListener {
93    public static final boolean DEBUG = false;
94    public static final boolean DEBUG_COMPAT_HELP = false;
95    public static final String TAG = "TabletStatusBar";
96
97
98    public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
99    public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
100    public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002;
101    public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003;
102    // 1020-1029 reserved for BaseStatusBar
103    public static final int MSG_SHOW_CHROME = 1030;
104    public static final int MSG_HIDE_CHROME = 1031;
105    public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040;
106    public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041;
107    public static final int MSG_OPEN_COMPAT_MODE_PANEL = 1050;
108    public static final int MSG_CLOSE_COMPAT_MODE_PANEL = 1051;
109    public static final int MSG_STOP_TICKER = 2000;
110
111    // Fitts' Law assistance for LatinIME; see policy.EventHole
112    private static final boolean FAKE_SPACE_BAR = true;
113
114    // Notification "peeking" (flyover preview of individual notifications)
115    final static boolean NOTIFICATION_PEEK_ENABLED = false;
116    final static int NOTIFICATION_PEEK_HOLD_THRESH = 200; // ms
117    final static int NOTIFICATION_PEEK_FADE_DELAY = 3000; // ms
118
119    // The height of the bar, as definied by the build.  It may be taller if we're plugged
120    // into hdmi.
121    int mNaturalBarHeight = -1;
122    int mIconSize = -1;
123    int mIconHPadding = -1;
124    int mNavIconWidth = -1;
125    int mMenuNavIconWidth = -1;
126    private int mMaxNotificationIcons = 5;
127
128    IWindowManager mWindowManager;
129
130    // tracking all current notifications
131    private NotificationData mNotificationData = new NotificationData();
132
133    TabletStatusBarView mStatusBarView;
134    View mNotificationArea;
135    View mNotificationTrigger;
136    NotificationIconArea mNotificationIconArea;
137    ViewGroup mNavigationArea;
138
139    boolean mNotificationDNDMode;
140    NotificationData.Entry mNotificationDNDDummyEntry;
141
142    ImageView mBackButton;
143    View mHomeButton;
144    View mMenuButton;
145    View mRecentButton;
146    private boolean mAltBackButtonEnabledForIme;
147
148    ViewGroup mFeedbackIconArea; // notification icons, IME icon, compat icon
149    InputMethodButton mInputMethodSwitchButton;
150    CompatModeButton mCompatModeButton;
151
152    NotificationPanel mNotificationPanel;
153    WindowManager.LayoutParams mNotificationPanelParams;
154    NotificationPeekPanel mNotificationPeekWindow;
155    ViewGroup mNotificationPeekRow;
156    int mNotificationPeekIndex;
157    IBinder mNotificationPeekKey;
158    LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight;
159
160    int mNotificationPeekTapDuration;
161    int mNotificationFlingVelocity;
162
163    ViewGroup mPile;
164
165    HeightReceiver mHeightReceiver;
166    BatteryController mBatteryController;
167    BluetoothController mBluetoothController;
168    LocationController mLocationController;
169    NetworkController mNetworkController;
170
171    ViewGroup mBarContents;
172
173    // hide system chrome ("lights out") support
174    View mShadow;
175
176    NotificationIconArea.IconLayout mIconLayout;
177
178    TabletTicker mTicker;
179
180    View mFakeSpaceBar;
181    KeyEvent mSpaceBarKeyEvent = null;
182
183    View mCompatibilityHelpDialog = null;
184
185    // for disabling the status bar
186    int mDisabled = 0;
187
188    private InputMethodsPanel mInputMethodsPanel;
189    private CompatModePanel mCompatModePanel;
190
191    private int mSystemUiVisibility = 0;
192    // used to notify status bar for suppressing notification LED
193    private boolean mPanelSlightlyVisible;
194
195    private int mNavigationIconHints = 0;
196
197    public Context getContext() { return mContext; }
198
199    @Override
200    protected void createAndAddWindows() {
201        addStatusBarWindow();
202        addPanelWindows();
203    }
204
205    private void addStatusBarWindow() {
206        final View sb = makeStatusBarView();
207        final int height = getStatusBarHeight();
208
209        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
210                ViewGroup.LayoutParams.MATCH_PARENT,
211                height,
212                WindowManager.LayoutParams.TYPE_STATUS_BAR,
213                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
214                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
215                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
216                // We use a pixel format of RGB565 for the status bar to save memory bandwidth and
217                // to ensure that the layer can be handled by HWComposer.  On some devices the
218                // HWComposer is unable to handle SW-rendered RGBX_8888 layers.
219                PixelFormat.RGB_565);
220
221        // the status bar should be in an overlay if possible
222        final Display defaultDisplay
223            = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
224                .getDefaultDisplay();
225
226        // We explicitly leave FLAG_HARDWARE_ACCELERATED out of the flags.  The status bar occupies
227        // very little screen real-estate and is updated fairly frequently.  By using CPU rendering
228        // for the status bar, we prevent the GPU from having to wake up just to do these small
229        // updates, which should help keep power consumption down.
230
231        lp.gravity = getStatusBarGravity();
232        lp.setTitle("StatusBar");
233        lp.packageName = mContext.getPackageName();
234        lp.windowAnimations = R.style.Animation_StatusBar;
235        WindowManagerImpl.getDefault().addView(sb, lp);
236    }
237
238    protected void addPanelWindows() {
239        final Context context = mContext;
240        final Resources res = mContext.getResources();
241
242        // Notification Panel
243        mNotificationPanel = (NotificationPanel)View.inflate(context,
244                R.layout.status_bar_notification_panel, null);
245        mNotificationPanel.setBar(this);
246        mNotificationPanel.show(false, false);
247        mNotificationPanel.setOnTouchListener(
248                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel));
249
250        // the battery icon
251        mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery));
252        mBatteryController.addLabelView(
253                (TextView)mNotificationPanel.findViewById(R.id.battery_text));
254
255        // Bt
256        mBluetoothController.addIconView(
257                (ImageView)mNotificationPanel.findViewById(R.id.bluetooth));
258
259        // network icons: either a combo icon that switches between mobile and data, or distinct
260        // mobile and data icons
261        final ImageView mobileRSSI =
262                (ImageView)mNotificationPanel.findViewById(R.id.mobile_signal);
263        if (mobileRSSI != null) {
264            mNetworkController.addPhoneSignalIconView(mobileRSSI);
265        }
266        final ImageView wifiRSSI =
267                (ImageView)mNotificationPanel.findViewById(R.id.wifi_signal);
268        if (wifiRSSI != null) {
269            mNetworkController.addWifiIconView(wifiRSSI);
270        }
271        mNetworkController.addWifiLabelView(
272                (TextView)mNotificationPanel.findViewById(R.id.wifi_text));
273
274        mNetworkController.addDataTypeIconView(
275                (ImageView)mNotificationPanel.findViewById(R.id.mobile_type));
276        mNetworkController.addMobileLabelView(
277                (TextView)mNotificationPanel.findViewById(R.id.mobile_text));
278        mNetworkController.addCombinedLabelView(
279                (TextView)mBarContents.findViewById(R.id.network_text));
280
281        mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
282
283        WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams(
284                res.getDimensionPixelSize(R.dimen.notification_panel_width),
285                getNotificationPanelHeight(),
286                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
287                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
288                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
289                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
290                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
291                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
292                PixelFormat.TRANSLUCENT);
293        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
294        lp.setTitle("NotificationPanel");
295        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
296                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
297        lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation
298//        lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
299
300        WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
301
302        // Notification preview window
303        if (NOTIFICATION_PEEK_ENABLED) {
304            mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context,
305                    R.layout.status_bar_notification_peek, null);
306            mNotificationPeekWindow.setBar(this);
307
308            mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content);
309            mNotificationPeekWindow.setVisibility(View.GONE);
310            mNotificationPeekWindow.setOnTouchListener(
311                    new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow));
312            mNotificationPeekScrubRight = new LayoutTransition();
313            mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING,
314                    ObjectAnimator.ofInt(null, "left", -512, 0));
315            mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING,
316                    ObjectAnimator.ofInt(null, "left", -512, 0));
317            mNotificationPeekScrubRight.setDuration(500);
318
319            mNotificationPeekScrubLeft = new LayoutTransition();
320            mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING,
321                    ObjectAnimator.ofInt(null, "left", 512, 0));
322            mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING,
323                    ObjectAnimator.ofInt(null, "left", 512, 0));
324            mNotificationPeekScrubLeft.setDuration(500);
325
326            // XXX: setIgnoreChildren?
327            lp = new WindowManager.LayoutParams(
328                    512, // ViewGroup.LayoutParams.WRAP_CONTENT,
329                    ViewGroup.LayoutParams.WRAP_CONTENT,
330                    WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
331                    WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
332                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
333                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
334                    PixelFormat.TRANSLUCENT);
335            lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
336            lp.y = res.getDimensionPixelOffset(R.dimen.peek_window_y_offset);
337            lp.setTitle("NotificationPeekWindow");
338            lp.windowAnimations = com.android.internal.R.style.Animation_Toast;
339
340            WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp);
341        }
342
343        // Recents Panel
344        mRecentTasksLoader = new RecentTasksLoader(context);
345        updateRecentsPanel();
346
347        // Input methods Panel
348        mInputMethodsPanel = (InputMethodsPanel) View.inflate(context,
349                R.layout.status_bar_input_methods_panel, null);
350        mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this);
351        mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener(
352                MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel));
353        mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton);
354        mStatusBarView.setIgnoreChildren(2, mInputMethodSwitchButton, mInputMethodsPanel);
355        lp = new WindowManager.LayoutParams(
356                ViewGroup.LayoutParams.WRAP_CONTENT,
357                ViewGroup.LayoutParams.WRAP_CONTENT,
358                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
359                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
360                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
361                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
362                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
363                PixelFormat.TRANSLUCENT);
364        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
365        lp.setTitle("InputMethodsPanel");
366        lp.windowAnimations = R.style.Animation_RecentPanel;
367
368        WindowManagerImpl.getDefault().addView(mInputMethodsPanel, lp);
369
370        // Compatibility mode selector panel
371        mCompatModePanel = (CompatModePanel) View.inflate(context,
372                R.layout.status_bar_compat_mode_panel, null);
373        mCompatModePanel.setOnTouchListener(new TouchOutsideListener(
374                MSG_CLOSE_COMPAT_MODE_PANEL, mCompatModePanel));
375        mCompatModePanel.setTrigger(mCompatModeButton);
376        mCompatModePanel.setVisibility(View.GONE);
377        mStatusBarView.setIgnoreChildren(3, mCompatModeButton, mCompatModePanel);
378        lp = new WindowManager.LayoutParams(
379                250,
380                ViewGroup.LayoutParams.WRAP_CONTENT,
381                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
382                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
383                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
384                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
385                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
386                PixelFormat.TRANSLUCENT);
387        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
388        lp.setTitle("CompatModePanel");
389        lp.windowAnimations = android.R.style.Animation_Dialog;
390
391        WindowManagerImpl.getDefault().addView(mCompatModePanel, lp);
392
393        mRecentButton.setOnTouchListener(mRecentsPanel);
394
395        mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
396        mPile.removeAllViews();
397
398        ScrollView scroller = (ScrollView)mPile.getParent();
399        scroller.setFillViewport(true);
400    }
401
402    private int getNotificationPanelHeight() {
403        final Resources res = mContext.getResources();
404        final Display d = WindowManagerImpl.getDefault().getDefaultDisplay();
405        final Point size = new Point();
406        d.getRealSize(size);
407        return Math.max(res.getDimensionPixelSize(R.dimen.notification_panel_min_height), size.y);
408    }
409
410    @Override
411    public void start() {
412        super.start(); // will add the main bar view
413    }
414
415    @Override
416    protected void onConfigurationChanged(Configuration newConfig) {
417        mHeightReceiver.updateHeight(); // display size may have changed
418        loadDimens();
419        mNotificationPanelParams.height = getNotificationPanelHeight();
420        WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel,
421                mNotificationPanelParams);
422        mRecentsPanel.updateValuesFromResources();
423    }
424
425    protected void loadDimens() {
426        final Resources res = mContext.getResources();
427
428        mNaturalBarHeight = res.getDimensionPixelSize(
429                com.android.internal.R.dimen.system_bar_height);
430
431        int newIconSize = res.getDimensionPixelSize(
432            com.android.internal.R.dimen.system_bar_icon_size);
433        int newIconHPadding = res.getDimensionPixelSize(
434            R.dimen.status_bar_icon_padding);
435        int newNavIconWidth = res.getDimensionPixelSize(R.dimen.navigation_key_width);
436        int newMenuNavIconWidth = res.getDimensionPixelSize(R.dimen.navigation_menu_key_width);
437
438        if (mNavigationArea != null && newNavIconWidth != mNavIconWidth) {
439            mNavIconWidth = newNavIconWidth;
440
441            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
442                     mNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT);
443            mBackButton.setLayoutParams(lp);
444            mHomeButton.setLayoutParams(lp);
445            mRecentButton.setLayoutParams(lp);
446        }
447
448        if (mNavigationArea != null && newMenuNavIconWidth != mMenuNavIconWidth) {
449            mMenuNavIconWidth = newMenuNavIconWidth;
450
451            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
452                     mMenuNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT);
453            mMenuButton.setLayoutParams(lp);
454        }
455
456        if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) {
457//            Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding);
458            mIconHPadding = newIconHPadding;
459            mIconSize = newIconSize;
460            reloadAllNotificationIcons(); // reload the tray
461        }
462
463        final int numIcons = res.getInteger(R.integer.config_maxNotificationIcons);
464        if (numIcons != mMaxNotificationIcons) {
465            mMaxNotificationIcons = numIcons;
466            if (DEBUG) Slog.d(TAG, "max notification icons: " + mMaxNotificationIcons);
467            reloadAllNotificationIcons();
468        }
469    }
470
471    public View getStatusBarView() {
472        return mStatusBarView;
473    }
474
475    protected View makeStatusBarView() {
476        final Context context = mContext;
477
478        mWindowManager = IWindowManager.Stub.asInterface(
479                ServiceManager.getService(Context.WINDOW_SERVICE));
480
481        // This guy will listen for HDMI plugged broadcasts so we can resize the
482        // status bar as appropriate.
483        mHeightReceiver = new HeightReceiver(mContext);
484        mHeightReceiver.registerReceiver();
485        loadDimens();
486
487        final TabletStatusBarView sb = (TabletStatusBarView)View.inflate(
488                context, R.layout.status_bar, null);
489        mStatusBarView = sb;
490
491        sb.setHandler(mHandler);
492
493        try {
494            // Sanity-check that someone hasn't set up the config wrong and asked for a navigation
495            // bar on a tablet that has only the system bar
496            if (mWindowManager.hasNavigationBar()) {
497                Slog.e(TAG, "Tablet device cannot show navigation bar and system bar");
498            }
499        } catch (RemoteException ex) {
500        }
501
502        mBarContents = (ViewGroup) sb.findViewById(R.id.bar_contents);
503
504        // the whole right-hand side of the bar
505        mNotificationArea = sb.findViewById(R.id.notificationArea);
506        if (!NOTIFICATION_PEEK_ENABLED) {
507            mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener());
508        }
509
510        // the button to open the notification area
511        mNotificationTrigger = sb.findViewById(R.id.notificationTrigger);
512        if (NOTIFICATION_PEEK_ENABLED) {
513            mNotificationTrigger.setOnTouchListener(new NotificationTriggerTouchListener());
514        }
515
516        // the more notifications icon
517        mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
518
519        // where the icons go
520        mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
521        if (NOTIFICATION_PEEK_ENABLED) {
522            mIconLayout.setOnTouchListener(new NotificationIconTouchListener());
523        }
524
525        ViewConfiguration vc = ViewConfiguration.get(context);
526        mNotificationPeekTapDuration = vc.getTapTimeout();
527        mNotificationFlingVelocity = 300; // px/s
528
529        mTicker = new TabletTicker(this);
530
531        // The icons
532        mLocationController = new LocationController(mContext); // will post a notification
533
534        mBatteryController = new BatteryController(mContext);
535        mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
536        mBluetoothController = new BluetoothController(mContext);
537        mBluetoothController.addIconView((ImageView)sb.findViewById(R.id.bluetooth));
538
539        mNetworkController = new NetworkController(mContext);
540        final SignalClusterView signalCluster =
541                (SignalClusterView)sb.findViewById(R.id.signal_cluster);
542        mNetworkController.addSignalCluster(signalCluster);
543
544        // The navigation buttons
545        mBackButton = (ImageView)sb.findViewById(R.id.back);
546        mNavigationArea = (ViewGroup) sb.findViewById(R.id.navigationArea);
547        mHomeButton = mNavigationArea.findViewById(R.id.home);
548        mMenuButton = mNavigationArea.findViewById(R.id.menu);
549        mRecentButton = mNavigationArea.findViewById(R.id.recent_apps);
550        mRecentButton.setOnClickListener(mOnClickListener);
551
552        LayoutTransition lt = new LayoutTransition();
553        lt.setDuration(250);
554        // don't wait for these transitions; we just want icons to fade in/out, not move around
555        lt.setDuration(LayoutTransition.CHANGE_APPEARING, 0);
556        lt.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 0);
557        lt.addTransitionListener(new LayoutTransition.TransitionListener() {
558            public void endTransition(LayoutTransition transition, ViewGroup container,
559                    View view, int transitionType) {
560                // ensure the menu button doesn't stick around on the status bar after it's been
561                // removed
562                mBarContents.invalidate();
563            }
564            public void startTransition(LayoutTransition transition, ViewGroup container,
565                    View view, int transitionType) {}
566        });
567        mNavigationArea.setLayoutTransition(lt);
568        // no multi-touch on the nav buttons
569        mNavigationArea.setMotionEventSplittingEnabled(false);
570
571        // The bar contents buttons
572        mFeedbackIconArea = (ViewGroup)sb.findViewById(R.id.feedbackIconArea);
573        mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton);
574        // Overwrite the lister
575        mInputMethodSwitchButton.setOnClickListener(mOnClickListener);
576
577        mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton);
578        mCompatModeButton.setOnClickListener(mOnClickListener);
579        mCompatModeButton.setVisibility(View.GONE);
580
581        // for redirecting errant bar taps to the IME
582        mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar);
583
584        // "shadows" of the status bar features, for lights-out mode
585        mShadow = sb.findViewById(R.id.bar_shadow);
586        mShadow.setOnTouchListener(
587            new View.OnTouchListener() {
588                public boolean onTouch(View v, MotionEvent ev) {
589                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
590                        // even though setting the systemUI visibility below will turn these views
591                        // on, we need them to come up faster so that they can catch this motion
592                        // event
593                        mShadow.setVisibility(View.GONE);
594                        mBarContents.setVisibility(View.VISIBLE);
595
596                        try {
597                            mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE);
598                        } catch (RemoteException ex) {
599                            // system process dead
600                        }
601                    }
602                    return false;
603                }
604            });
605
606        // tuning parameters
607        final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 600;
608        final int LIGHTS_GOING_OUT_SHADOW_DURATION = 1000;
609        final int LIGHTS_GOING_OUT_SHADOW_DELAY    = 500;
610
611        final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200;
612//        final int LIGHTS_COMING_UP_SYSBAR_DELAY    = 50;
613        final int LIGHTS_COMING_UP_SHADOW_DURATION = 0;
614
615        LayoutTransition xition = new LayoutTransition();
616        xition.setAnimator(LayoutTransition.APPEARING,
617               ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f));
618        xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION);
619        xition.setStartDelay(LayoutTransition.APPEARING, 0);
620        xition.setAnimator(LayoutTransition.DISAPPEARING,
621               ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
622        xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION);
623        xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
624        ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition);
625
626        xition = new LayoutTransition();
627        xition.setAnimator(LayoutTransition.APPEARING,
628               ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
629        xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION);
630        xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY);
631        xition.setAnimator(LayoutTransition.DISAPPEARING,
632               ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
633        xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION);
634        xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
635        ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition);
636
637        // set the initial view visibility
638        setAreThereNotifications();
639
640        mHeightReceiver.addOnBarHeightChangedListener(this);
641
642        // receive broadcasts
643        IntentFilter filter = new IntentFilter();
644        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
645        filter.addAction(Intent.ACTION_SCREEN_OFF);
646        context.registerReceiver(mBroadcastReceiver, filter);
647
648        return sb;
649    }
650
651    @Override
652    protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) {
653        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
654                (int) mContext.getResources().getDimension(R.dimen.status_bar_recents_width),
655                ViewGroup.LayoutParams.MATCH_PARENT,
656                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
657                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
658                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
659                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
660                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
661                PixelFormat.TRANSLUCENT);
662        lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
663        lp.setTitle("RecentsPanel");
664        lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
665        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
666            | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
667
668        return lp;
669    }
670
671    protected void updateRecentsPanel() {
672        super.updateRecentsPanel();
673        mRecentsPanel.setStatusBarView(mStatusBarView);
674    }
675
676    public int getStatusBarHeight() {
677        return mHeightReceiver.getHeight();
678    }
679
680    protected int getStatusBarGravity() {
681        return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
682    }
683
684    public void onBarHeightChanged(int height) {
685        final WindowManager.LayoutParams lp
686                = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams();
687        if (lp == null) {
688            // haven't been added yet
689            return;
690        }
691        if (lp.height != height) {
692            lp.height = height;
693            final WindowManager wm = WindowManagerImpl.getDefault();
694            wm.updateViewLayout(mStatusBarView, lp);
695        }
696    }
697
698    @Override
699    protected BaseStatusBar.H createHandler() {
700        return new TabletStatusBar.H();
701    }
702
703    private class H extends BaseStatusBar.H {
704        public void handleMessage(Message m) {
705            super.handleMessage(m);
706            switch (m.what) {
707                case MSG_OPEN_NOTIFICATION_PEEK:
708                    if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1);
709
710                    if (m.arg1 >= 0) {
711                        final int N = mNotificationData.size();
712
713                        if (!mNotificationDNDMode) {
714                            if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
715                                NotificationData.Entry entry = mNotificationData.get(N-1-mNotificationPeekIndex);
716                                entry.icon.setBackgroundColor(0);
717                                mNotificationPeekIndex = -1;
718                                mNotificationPeekKey = null;
719                            }
720                        }
721
722                        final int peekIndex = m.arg1;
723                        if (peekIndex < N) {
724                            //Slog.d(TAG, "loading peek: " + peekIndex);
725                            NotificationData.Entry entry =
726                                mNotificationDNDMode
727                                    ? mNotificationDNDDummyEntry
728                                    : mNotificationData.get(N-1-peekIndex);
729                            NotificationData.Entry copy = new NotificationData.Entry(
730                                    entry.key,
731                                    entry.notification,
732                                    entry.icon);
733                            inflateViews(copy, mNotificationPeekRow);
734
735                            if (mNotificationDNDMode) {
736                                copy.content.setOnClickListener(new View.OnClickListener() {
737                                    public void onClick(View v) {
738                                        SharedPreferences.Editor editor = Prefs.edit(mContext);
739                                        editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false);
740                                        editor.apply();
741                                        animateCollapse();
742                                        visibilityChanged(false);
743                                    }
744                                });
745                            }
746
747                            entry.icon.setBackgroundColor(0x20FFFFFF);
748
749//                          mNotificationPeekRow.setLayoutTransition(
750//                              peekIndex < mNotificationPeekIndex
751//                                  ? mNotificationPeekScrubLeft
752//                                  : mNotificationPeekScrubRight);
753
754                            mNotificationPeekRow.removeAllViews();
755                            mNotificationPeekRow.addView(copy.row);
756
757                            mNotificationPeekWindow.setVisibility(View.VISIBLE);
758                            mNotificationPanel.show(false, true);
759
760                            mNotificationPeekIndex = peekIndex;
761                            mNotificationPeekKey = entry.key;
762                        }
763                    }
764                    break;
765                case MSG_CLOSE_NOTIFICATION_PEEK:
766                    if (DEBUG) Slog.d(TAG, "closing notification peek window");
767                    mNotificationPeekWindow.setVisibility(View.GONE);
768                    mNotificationPeekRow.removeAllViews();
769
770                    final int N = mNotificationData.size();
771                    if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
772                        NotificationData.Entry entry =
773                            mNotificationDNDMode
774                                ? mNotificationDNDDummyEntry
775                                : mNotificationData.get(N-1-mNotificationPeekIndex);
776                        entry.icon.setBackgroundColor(0);
777                    }
778
779                    mNotificationPeekIndex = -1;
780                    mNotificationPeekKey = null;
781                    break;
782                case MSG_OPEN_NOTIFICATION_PANEL:
783                    if (DEBUG) Slog.d(TAG, "opening notifications panel");
784                    if (!mNotificationPanel.isShowing()) {
785                        if (NOTIFICATION_PEEK_ENABLED) {
786                            mNotificationPeekWindow.setVisibility(View.GONE);
787                        }
788                        mNotificationPanel.show(true, true);
789                        mNotificationArea.setVisibility(View.INVISIBLE);
790                        mTicker.halt();
791                    }
792                    break;
793                case MSG_CLOSE_NOTIFICATION_PANEL:
794                    if (DEBUG) Slog.d(TAG, "closing notifications panel");
795                    if (mNotificationPanel.isShowing()) {
796                        mNotificationPanel.show(false, true);
797                        mNotificationArea.setVisibility(View.VISIBLE);
798                    }
799                    break;
800                case MSG_OPEN_INPUT_METHODS_PANEL:
801                    if (DEBUG) Slog.d(TAG, "opening input methods panel");
802                    if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel();
803                    break;
804                case MSG_CLOSE_INPUT_METHODS_PANEL:
805                    if (DEBUG) Slog.d(TAG, "closing input methods panel");
806                    if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false);
807                    break;
808                case MSG_OPEN_COMPAT_MODE_PANEL:
809                    if (DEBUG) Slog.d(TAG, "opening compat panel");
810                    if (mCompatModePanel != null) mCompatModePanel.openPanel();
811                    break;
812                case MSG_CLOSE_COMPAT_MODE_PANEL:
813                    if (DEBUG) Slog.d(TAG, "closing compat panel");
814                    if (mCompatModePanel != null) mCompatModePanel.closePanel();
815                    break;
816                case MSG_SHOW_CHROME:
817                    if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)");
818                    mBarContents.setVisibility(View.VISIBLE);
819                    mShadow.setVisibility(View.GONE);
820                    mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
821                    notifyUiVisibilityChanged();
822                    break;
823                case MSG_HIDE_CHROME:
824                    if (DEBUG) Slog.d(TAG, "showing shadows (lights out)");
825                    animateCollapse();
826                    visibilityChanged(false);
827                    mBarContents.setVisibility(View.GONE);
828                    mShadow.setVisibility(View.VISIBLE);
829                    mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
830                    notifyUiVisibilityChanged();
831                    break;
832                case MSG_STOP_TICKER:
833                    mTicker.halt();
834                    break;
835            }
836        }
837    }
838
839    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
840        if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon);
841    }
842
843    public void updateIcon(String slot, int index, int viewIndex,
844            StatusBarIcon old, StatusBarIcon icon) {
845        if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon);
846    }
847
848    public void removeIcon(String slot, int index, int viewIndex) {
849        if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")");
850    }
851
852    public void addNotification(IBinder key, StatusBarNotification notification) {
853        if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
854        addNotificationViews(key, notification);
855
856        final boolean immersive = isImmersive();
857        if (false && immersive) {
858            // TODO: immersive mode popups for tablet
859        } else if (notification.notification.fullScreenIntent != null) {
860            // not immersive & a full-screen alert should be shown
861            Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;"
862                    + " sending fullScreenIntent");
863            try {
864                notification.notification.fullScreenIntent.send();
865            } catch (PendingIntent.CanceledException e) {
866            }
867        } else {
868            tick(key, notification, true);
869        }
870
871        setAreThereNotifications();
872    }
873
874    public void updateNotification(IBinder key, StatusBarNotification notification) {
875        if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
876
877        final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
878        if (oldEntry == null) {
879            Slog.w(TAG, "updateNotification for unknown key: " + key);
880            return;
881        }
882
883        final StatusBarNotification oldNotification = oldEntry.notification;
884        final RemoteViews oldContentView = oldNotification.notification.contentView;
885
886        final RemoteViews contentView = notification.notification.contentView;
887
888        if (DEBUG) {
889            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
890                    + " ongoing=" + oldNotification.isOngoing()
891                    + " expanded=" + oldEntry.expanded
892                    + " contentView=" + oldContentView
893                    + " rowParent=" + oldEntry.row.getParent());
894            Slog.d(TAG, "new notification: when=" + notification.notification.when
895                    + " ongoing=" + oldNotification.isOngoing()
896                    + " contentView=" + contentView);
897        }
898
899        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
900        // didn't change.
901        boolean contentsUnchanged = oldEntry.expanded != null
902                && contentView != null && oldContentView != null
903                && contentView.getPackage() != null
904                && oldContentView.getPackage() != null
905                && oldContentView.getPackage().equals(contentView.getPackage())
906                && oldContentView.getLayoutId() == contentView.getLayoutId();
907        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
908        boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
909                && notification.score == oldNotification.score;
910                // score now encompasses/supersedes isOngoing()
911        boolean updateTicker = notification.notification.tickerText != null
912                && !TextUtils.equals(notification.notification.tickerText,
913                        oldEntry.notification.notification.tickerText);
914        boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1;
915        if (contentsUnchanged && (orderUnchanged || isLastAnyway)) {
916            if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
917            oldEntry.notification = notification;
918            try {
919                // Reapply the RemoteViews
920                contentView.reapply(mContext, oldEntry.content);
921                // update the contentIntent
922                final PendingIntent contentIntent = notification.notification.contentIntent;
923                if (contentIntent != null) {
924                    final View.OnClickListener listener = new NotificationClicker(contentIntent,
925                            notification.pkg, notification.tag, notification.id);
926                    oldEntry.content.setOnClickListener(listener);
927                } else {
928                    oldEntry.content.setOnClickListener(null);
929                }
930                // Update the icon.
931                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
932                        notification.notification.icon, notification.notification.iconLevel,
933                        notification.notification.number,
934                        notification.notification.tickerText);
935                if (!oldEntry.icon.set(ic)) {
936                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
937                    return;
938                }
939
940                if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) {
941                    // must update the peek window
942                    Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
943                    peekMsg.arg1 = mNotificationPeekIndex;
944                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
945                    mHandler.sendMessage(peekMsg);
946                }
947            }
948            catch (RuntimeException e) {
949                // It failed to add cleanly.  Log, and remove the view from the panel.
950                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
951                removeNotificationViews(key);
952                addNotificationViews(key, notification);
953            }
954        } else {
955            if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
956            removeNotificationViews(key);
957            addNotificationViews(key, notification);
958        }
959
960        // Restart the ticker if it's still running
961        if (updateTicker) {
962            mTicker.halt();
963            tick(key, notification, false);
964        }
965
966        setAreThereNotifications();
967    }
968
969    public void removeNotification(IBinder key) {
970        if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ")");
971        removeNotificationViews(key);
972        mTicker.remove(key);
973        setAreThereNotifications();
974    }
975
976    public void showClock(boolean show) {
977        View clock = mBarContents.findViewById(R.id.clock);
978        View network_text = mBarContents.findViewById(R.id.network_text);
979        if (clock != null) {
980            clock.setVisibility(show ? View.VISIBLE : View.GONE);
981        }
982        if (network_text != null) {
983            network_text.setVisibility((!show) ? View.VISIBLE : View.GONE);
984        }
985    }
986
987    public void disable(int state) {
988        int old = mDisabled;
989        int diff = state ^ old;
990        mDisabled = state;
991
992        // act accordingly
993        if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
994            boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
995            Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes"));
996            showClock(show);
997        }
998        if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
999            boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0;
1000            Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes"));
1001            mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE);
1002        }
1003        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
1004            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
1005                Slog.i(TAG, "DISABLE_EXPAND: yes");
1006                animateCollapse();
1007                visibilityChanged(false);
1008            }
1009        }
1010        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
1011            mNotificationDNDMode = Prefs.read(mContext)
1012                        .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT);
1013
1014            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
1015                Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":""));
1016                mTicker.halt();
1017            } else {
1018                Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":""));
1019            }
1020
1021            // refresh icons to show either notifications or the DND message
1022            reloadAllNotificationIcons();
1023        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
1024            if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
1025                mTicker.halt();
1026            }
1027        }
1028        if ((diff & (StatusBarManager.DISABLE_RECENT
1029                        | StatusBarManager.DISABLE_BACK
1030                        | StatusBarManager.DISABLE_HOME)) != 0) {
1031            setNavigationVisibility(state);
1032
1033            if ((state & StatusBarManager.DISABLE_RECENT) != 0) {
1034                // close recents if it's visible
1035                mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
1036                mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
1037            }
1038        }
1039    }
1040
1041    private void setNavigationVisibility(int visibility) {
1042        boolean disableHome = ((visibility & StatusBarManager.DISABLE_HOME) != 0);
1043        boolean disableRecent = ((visibility & StatusBarManager.DISABLE_RECENT) != 0);
1044        boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0);
1045
1046        mBackButton.setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
1047        mHomeButton.setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
1048        mRecentButton.setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
1049
1050        mInputMethodSwitchButton.setScreenLocked(
1051                (visibility & StatusBarManager.DISABLE_SYSTEM_INFO) != 0);
1052    }
1053
1054    private boolean hasTicker(Notification n) {
1055        return n.tickerView != null || !TextUtils.isEmpty(n.tickerText);
1056    }
1057
1058    private void tick(IBinder key, StatusBarNotification n, boolean firstTime) {
1059        // Don't show the ticker when the windowshade is open.
1060        if (mNotificationPanel.isShowing()) {
1061            return;
1062        }
1063        // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification
1064        // if it's a new notification.
1065        if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) {
1066            return;
1067        }
1068        // Show the ticker if one is requested. Also don't do this
1069        // until status bar window is attached to the window manager,
1070        // because...  well, what's the point otherwise?  And trying to
1071        // run a ticker without being attached will crash!
1072        if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
1073            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
1074                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
1075                mTicker.add(key, n);
1076                mFeedbackIconArea.setVisibility(View.GONE);
1077            }
1078        }
1079    }
1080
1081    // called by TabletTicker when it's done with all queued ticks
1082    public void doneTicking() {
1083        mFeedbackIconArea.setVisibility(View.VISIBLE);
1084    }
1085
1086    public void animateExpand() {
1087        if (NOTIFICATION_PEEK_ENABLED) {
1088            mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
1089            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1090            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1091        }
1092        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
1093        mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
1094    }
1095
1096    public void animateCollapse() {
1097        animateCollapse(false);
1098    }
1099
1100    private void animateCollapse(boolean excludeRecents) {
1101        mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
1102        mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
1103        if (!excludeRecents) {
1104            mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
1105            mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
1106        }
1107        mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL);
1108        mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL);
1109        mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL);
1110        mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL);
1111        if (NOTIFICATION_PEEK_ENABLED) {
1112            mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
1113            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1114        }
1115    }
1116
1117    /**
1118     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
1119     * This was added last-minute and is inconsistent with the way the rest of the notifications
1120     * are handled, because the notification isn't really cancelled.  The lights are just
1121     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
1122     * this is what he wants. (see bug 1131461)
1123     */
1124    void visibilityChanged(boolean visible) {
1125        if (mPanelSlightlyVisible != visible) {
1126            mPanelSlightlyVisible = visible;
1127            try {
1128                mBarService.onPanelRevealed();
1129            } catch (RemoteException ex) {
1130                // Won't fail unless the world has ended.
1131            }
1132        }
1133    }
1134
1135    @Override // CommandQueue
1136    public void setNavigationIconHints(int hints) {
1137        if (hints == mNavigationIconHints) return;
1138
1139        if (DEBUG) {
1140            android.widget.Toast.makeText(mContext,
1141                "Navigation icon hints = " + hints,
1142                500).show();
1143        }
1144
1145        mNavigationIconHints = hints;
1146
1147        mBackButton.setAlpha(
1148            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f);
1149        mHomeButton.setAlpha(
1150            (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f);
1151        mRecentButton.setAlpha(
1152            (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f);
1153
1154        mBackButton.setImageResource(
1155            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT))
1156                ? R.drawable.ic_sysbar_back_ime
1157                : R.drawable.ic_sysbar_back);
1158    }
1159
1160    private void notifyUiVisibilityChanged() {
1161        try {
1162            mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility);
1163        } catch (RemoteException ex) {
1164        }
1165    }
1166
1167    @Override // CommandQueue
1168    public void setSystemUiVisibility(int vis, int mask) {
1169        final int oldVal = mSystemUiVisibility;
1170        final int newVal = (oldVal&~mask) | (vis&mask);
1171        final int diff = newVal ^ oldVal;
1172
1173        if (diff != 0) {
1174            mSystemUiVisibility = newVal;
1175
1176            if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) {
1177                mHandler.removeMessages(MSG_HIDE_CHROME);
1178                mHandler.removeMessages(MSG_SHOW_CHROME);
1179                mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE)
1180                        ? MSG_SHOW_CHROME : MSG_HIDE_CHROME);
1181            }
1182
1183            notifyUiVisibilityChanged();
1184        }
1185    }
1186
1187    public void setLightsOn(boolean on) {
1188        // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app
1189        // that can't handle lights-out mode.
1190        if (mMenuButton.getVisibility() == View.VISIBLE) {
1191            on = true;
1192        }
1193
1194        Slog.v(TAG, "setLightsOn(" + on + ")");
1195        if (on) {
1196            setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE);
1197        } else {
1198            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, View.SYSTEM_UI_FLAG_LOW_PROFILE);
1199        }
1200    }
1201
1202    public void topAppWindowChanged(boolean showMenu) {
1203        if (DEBUG) {
1204            Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button");
1205        }
1206        mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE);
1207
1208        // See above re: lights-out policy for legacy apps.
1209        if (showMenu) setLightsOn(true);
1210
1211        mCompatModeButton.refresh();
1212        if (mCompatModeButton.getVisibility() == View.VISIBLE) {
1213            if (DEBUG_COMPAT_HELP
1214                    || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) {
1215                showCompatibilityHelp();
1216            }
1217        } else {
1218            hideCompatibilityHelp();
1219            mCompatModePanel.closePanel();
1220        }
1221    }
1222
1223    private void showCompatibilityHelp() {
1224        if (mCompatibilityHelpDialog != null) {
1225            return;
1226        }
1227
1228        mCompatibilityHelpDialog = View.inflate(mContext, R.layout.compat_mode_help, null);
1229        View button = mCompatibilityHelpDialog.findViewById(R.id.button);
1230
1231        button.setOnClickListener(new View.OnClickListener() {
1232            @Override
1233            public void onClick(View v) {
1234                hideCompatibilityHelp();
1235                SharedPreferences.Editor editor = Prefs.edit(mContext);
1236                editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true);
1237                editor.apply();
1238            }
1239        });
1240
1241        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1242                ViewGroup.LayoutParams.MATCH_PARENT,
1243                ViewGroup.LayoutParams.MATCH_PARENT,
1244                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
1245                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1246                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
1247                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
1248                PixelFormat.TRANSLUCENT);
1249        lp.setTitle("CompatibilityModeDialog");
1250        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
1251                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
1252        lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
1253
1254        WindowManagerImpl.getDefault().addView(mCompatibilityHelpDialog, lp);
1255    }
1256
1257    private void hideCompatibilityHelp() {
1258        if (mCompatibilityHelpDialog != null) {
1259            WindowManagerImpl.getDefault().removeView(mCompatibilityHelpDialog);
1260            mCompatibilityHelpDialog = null;
1261        }
1262    }
1263
1264    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1265        mInputMethodSwitchButton.setImeWindowStatus(token,
1266                (vis & InputMethodService.IME_ACTIVE) != 0);
1267        updateNotificationIcons();
1268        mInputMethodsPanel.setImeToken(token);
1269
1270        boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS)
1271            || ((vis & InputMethodService.IME_VISIBLE) != 0);
1272        mAltBackButtonEnabledForIme = altBack;
1273
1274        mCommandQueue.setNavigationIconHints(
1275                altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT)
1276                        : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT));
1277
1278        if (FAKE_SPACE_BAR) {
1279            mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0)
1280                    ? View.VISIBLE : View.GONE);
1281        }
1282    }
1283
1284    @Override
1285    public void onRecentsPanelVisibilityChanged(boolean visible) {
1286        boolean altBack = visible || mAltBackButtonEnabledForIme;
1287        mCommandQueue.setNavigationIconHints(
1288                altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT)
1289                        : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT));
1290    }
1291
1292    @Override
1293    public void setHardKeyboardStatus(boolean available, boolean enabled) {
1294        if (DEBUG) {
1295            Slog.d(TAG, "Set hard keyboard status: available=" + available
1296                    + ", enabled=" + enabled);
1297        }
1298        mInputMethodSwitchButton.setHardKeyboardStatus(available);
1299        updateNotificationIcons();
1300        mInputMethodsPanel.setHardKeyboardStatus(available, enabled);
1301    }
1302
1303    @Override
1304    public void onHardKeyboardEnabledChange(boolean enabled) {
1305        try {
1306            mBarService.setHardKeyboardEnabled(enabled);
1307        } catch (RemoteException ex) {
1308        }
1309    }
1310
1311    private boolean isImmersive() {
1312        try {
1313            return ActivityManagerNative.getDefault().isTopActivityImmersive();
1314            //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
1315        } catch (RemoteException ex) {
1316            // the end is nigh
1317            return false;
1318        }
1319    }
1320
1321    private void setAreThereNotifications() {
1322        if (mNotificationPanel != null) {
1323            mNotificationPanel.setClearable(mNotificationData.hasClearableItems());
1324        }
1325    }
1326
1327    /**
1328     * Cancel this notification and tell the status bar service about the failure. Hold no locks.
1329     */
1330    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
1331        removeNotification(key);
1332        try {
1333            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
1334        } catch (RemoteException ex) {
1335            // The end is nigh.
1336        }
1337    }
1338
1339    private void sendKey(KeyEvent key) {
1340        try {
1341            if (DEBUG) Slog.d(TAG, "injecting key event: " + key);
1342            mWindowManager.injectInputEventNoWait(key);
1343        } catch (RemoteException ex) {
1344        }
1345    }
1346
1347    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
1348        public void onClick(View v) {
1349            if (v == mRecentButton) {
1350                onClickRecentButton();
1351            } else if (v == mInputMethodSwitchButton) {
1352                onClickInputMethodSwitchButton();
1353            } else if (v == mCompatModeButton) {
1354                onClickCompatModeButton();
1355            }
1356        }
1357    };
1358
1359    public void onClickRecentButton() {
1360        if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled);
1361        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
1362            int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
1363                ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
1364            mHandler.removeMessages(msg);
1365            mHandler.sendEmptyMessage(msg);
1366        }
1367    }
1368
1369    public void onClickInputMethodSwitchButton() {
1370        if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled);
1371        int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ?
1372                MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL;
1373        mHandler.removeMessages(msg);
1374        mHandler.sendEmptyMessage(msg);
1375    }
1376
1377    public void onClickCompatModeButton() {
1378        int msg = (mCompatModePanel.getVisibility() == View.GONE) ?
1379                MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL;
1380        mHandler.removeMessages(msg);
1381        mHandler.sendEmptyMessage(msg);
1382    }
1383
1384    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
1385        return new NotificationClicker(intent, pkg, tag, id);
1386    }
1387
1388    private class NotificationClicker implements View.OnClickListener {
1389        private PendingIntent mIntent;
1390        private String mPkg;
1391        private String mTag;
1392        private int mId;
1393
1394        NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
1395            mIntent = intent;
1396            mPkg = pkg;
1397            mTag = tag;
1398            mId = id;
1399        }
1400
1401        public void onClick(View v) {
1402            try {
1403                // The intent we are sending is for the application, which
1404                // won't have permission to immediately start an activity after
1405                // the user switches to home.  We know it is safe to do at this
1406                // point, so make sure new activity switches are now allowed.
1407                ActivityManagerNative.getDefault().resumeAppSwitches();
1408                // Also, notifications can be launched from the lock screen,
1409                // so dismiss the lock screen when the activity starts.
1410                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
1411            } catch (RemoteException e) {
1412            }
1413
1414            if (mIntent != null) {
1415                int[] pos = new int[2];
1416                v.getLocationOnScreen(pos);
1417                Intent overlay = new Intent();
1418                overlay.setSourceBounds(
1419                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
1420                try {
1421                    mIntent.send(mContext, 0, overlay);
1422
1423                } catch (PendingIntent.CanceledException e) {
1424                    // the stack trace isn't very helpful here.  Just log the exception message.
1425                    Slog.w(TAG, "Sending contentIntent failed: " + e);
1426                }
1427
1428                KeyguardManager kgm =
1429                    (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
1430                if (kgm != null) kgm.exitKeyguardSecurely(null);
1431            }
1432
1433            try {
1434                mBarService.onNotificationClick(mPkg, mTag, mId);
1435            } catch (RemoteException ex) {
1436                // system process is dead if we're here.
1437            }
1438
1439            // close the shade if it was open
1440            animateCollapse();
1441            visibilityChanged(false);
1442
1443            // If this click was on the intruder alert, hide that instead
1444//            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
1445        }
1446    }
1447
1448    StatusBarNotification removeNotificationViews(IBinder key) {
1449        NotificationData.Entry entry = mNotificationData.remove(key);
1450        if (entry == null) {
1451            Slog.w(TAG, "removeNotification for unknown key: " + key);
1452            return null;
1453        }
1454        // Remove the expanded view.
1455        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
1456        if (rowParent != null) rowParent.removeView(entry.row);
1457
1458        if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) {
1459            // must close the peek as well, since it's gone
1460            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1461        }
1462        // Remove the icon.
1463//        ViewGroup iconParent = (ViewGroup)entry.icon.getParent();
1464//        if (iconParent != null) iconParent.removeView(entry.icon);
1465        updateNotificationIcons();
1466
1467        return entry.notification;
1468    }
1469
1470    private class NotificationTriggerTouchListener implements View.OnTouchListener {
1471        VelocityTracker mVT;
1472        float mInitialTouchX, mInitialTouchY;
1473        int mTouchSlop;
1474
1475        public NotificationTriggerTouchListener() {
1476            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
1477        }
1478
1479        private Runnable mHiliteOnR = new Runnable() { public void run() {
1480            mNotificationArea.setBackgroundResource(
1481                com.android.internal.R.drawable.list_selector_pressed_holo_dark);
1482        }};
1483        public void hilite(final boolean on) {
1484            if (on) {
1485                mNotificationArea.postDelayed(mHiliteOnR, 100);
1486            } else {
1487                mNotificationArea.removeCallbacks(mHiliteOnR);
1488                mNotificationArea.setBackgroundDrawable(null);
1489            }
1490        }
1491
1492        public boolean onTouch(View v, MotionEvent event) {
1493//            Slog.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)",
1494//                        event.getX(),
1495//                        event.getY(),
1496//                        mInitialTouchX,
1497//                        mInitialTouchY));
1498
1499            if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
1500                return true;
1501            }
1502
1503            final int action = event.getAction();
1504            switch (action) {
1505                case MotionEvent.ACTION_DOWN:
1506                    mVT = VelocityTracker.obtain();
1507                    mInitialTouchX = event.getX();
1508                    mInitialTouchY = event.getY();
1509                    hilite(true);
1510                    // fall through
1511                case MotionEvent.ACTION_OUTSIDE:
1512                case MotionEvent.ACTION_MOVE:
1513                    // check for fling
1514                    if (mVT != null) {
1515                        mVT.addMovement(event);
1516                        mVT.computeCurrentVelocity(1000); // pixels per second
1517                        // require a little more oomph once we're already in peekaboo mode
1518                        if (mVT.getYVelocity() < -mNotificationFlingVelocity) {
1519                            animateExpand();
1520                            visibilityChanged(true);
1521                            hilite(false);
1522                            mVT.recycle();
1523                            mVT = null;
1524                        }
1525                    }
1526                    return true;
1527                case MotionEvent.ACTION_UP:
1528                case MotionEvent.ACTION_CANCEL:
1529                    hilite(false);
1530                    if (mVT != null) {
1531                        if (action == MotionEvent.ACTION_UP
1532                         // was this a sloppy tap?
1533                         && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop
1534                         && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3)
1535                         // dragging off the bottom doesn't count
1536                         && (int)event.getY() < v.getBottom()) {
1537                            animateExpand();
1538                            visibilityChanged(true);
1539                            v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
1540                            v.playSoundEffect(SoundEffectConstants.CLICK);
1541                        }
1542
1543                        mVT.recycle();
1544                        mVT = null;
1545                        return true;
1546                    }
1547            }
1548            return false;
1549        }
1550    }
1551
1552    public void resetNotificationPeekFadeTimer() {
1553        if (DEBUG) {
1554            Slog.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY
1555                + "ms from now");
1556        }
1557        mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
1558        mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK,
1559                NOTIFICATION_PEEK_FADE_DELAY);
1560    }
1561
1562    private class NotificationIconTouchListener implements View.OnTouchListener {
1563        VelocityTracker mVT;
1564        int mPeekIndex;
1565        float mInitialTouchX, mInitialTouchY;
1566        int mTouchSlop;
1567
1568        public NotificationIconTouchListener() {
1569            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
1570        }
1571
1572        public boolean onTouch(View v, MotionEvent event) {
1573            boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
1574            boolean panelShowing = mNotificationPanel.isShowing();
1575            if (panelShowing) return false;
1576
1577            int numIcons = mIconLayout.getChildCount();
1578            int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth());
1579            if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1;
1580            else if (newPeekIndex < 0) newPeekIndex = 0;
1581
1582            final int action = event.getAction();
1583            switch (action) {
1584                case MotionEvent.ACTION_DOWN:
1585                    mVT = VelocityTracker.obtain();
1586                    mInitialTouchX = event.getX();
1587                    mInitialTouchY = event.getY();
1588                    mPeekIndex = -1;
1589
1590                    // fall through
1591                case MotionEvent.ACTION_OUTSIDE:
1592                case MotionEvent.ACTION_MOVE:
1593                    // peek and switch icons if necessary
1594
1595                    if (newPeekIndex != mPeekIndex) {
1596                        mPeekIndex = newPeekIndex;
1597
1598                        if (DEBUG) Slog.d(TAG, "will peek at notification #" + mPeekIndex);
1599                        Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
1600                        peekMsg.arg1 = mPeekIndex;
1601
1602                        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1603
1604                        if (peeking) {
1605                            // no delay if we're scrubbing left-right
1606                            mHandler.sendMessage(peekMsg);
1607                        } else {
1608                            // wait for fling
1609                            mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH);
1610                        }
1611                    }
1612
1613                    // check for fling
1614                    if (mVT != null) {
1615                        mVT.addMovement(event);
1616                        mVT.computeCurrentVelocity(1000); // pixels per second
1617                        // require a little more oomph once we're already in peekaboo mode
1618                        if (!panelShowing && (
1619                               (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
1620                            || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
1621                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1622                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
1623                            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1624                            mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
1625                        }
1626                    }
1627                    return true;
1628                case MotionEvent.ACTION_UP:
1629                case MotionEvent.ACTION_CANCEL:
1630                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1631                    if (!peeking) {
1632                        if (action == MotionEvent.ACTION_UP
1633                                // was this a sloppy tap?
1634                                && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop
1635                                && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3)
1636                                // dragging off the bottom doesn't count
1637                                && (int)event.getY() < v.getBottom()) {
1638                            Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
1639                            peekMsg.arg1 = mPeekIndex;
1640                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1641                            mHandler.sendMessage(peekMsg);
1642
1643                            v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
1644                            v.playSoundEffect(SoundEffectConstants.CLICK);
1645
1646                            peeking = true; // not technically true yet, but the next line will run
1647                        }
1648                    }
1649
1650                    if (peeking) {
1651                        resetNotificationPeekFadeTimer();
1652                    }
1653
1654                    mVT.recycle();
1655                    mVT = null;
1656                    return true;
1657            }
1658            return false;
1659        }
1660    }
1661
1662    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
1663        if (DEBUG) {
1664            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
1665        }
1666        // Construct the icon.
1667        final StatusBarIconView iconView = new StatusBarIconView(mContext,
1668                notification.pkg + "/0x" + Integer.toHexString(notification.id),
1669                notification.notification);
1670        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1671
1672        final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
1673                    notification.notification.icon,
1674                    notification.notification.iconLevel,
1675                    notification.notification.number,
1676                    notification.notification.tickerText);
1677        if (!iconView.set(ic)) {
1678            handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
1679            return null;
1680        }
1681        // Construct the expanded view.
1682        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
1683        if (!inflateViews(entry, mPile)) {
1684            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
1685                    + notification);
1686            return null;
1687        }
1688
1689        // Add the icon.
1690        int pos = mNotificationData.add(entry);
1691        if (DEBUG) {
1692            Slog.d(TAG, "addNotificationViews: added at " + pos);
1693        }
1694        updateNotificationIcons();
1695
1696        return iconView;
1697    }
1698
1699    private void reloadAllNotificationIcons() {
1700        if (mIconLayout == null) return;
1701        mIconLayout.removeAllViews();
1702        updateNotificationIcons();
1703    }
1704
1705    private void updateNotificationIcons() {
1706        // XXX: need to implement a new limited linear layout class
1707        // to avoid removing & readding everything
1708
1709        if (mIconLayout == null) return;
1710
1711        // first, populate the main notification panel
1712        loadNotificationPanel();
1713
1714        final LinearLayout.LayoutParams params
1715            = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
1716
1717        // alternate behavior in DND mode
1718        if (mNotificationDNDMode) {
1719            if (mIconLayout.getChildCount() == 0) {
1720                final Notification dndNotification = new Notification.Builder(mContext)
1721                    .setContentTitle(mContext.getText(R.string.notifications_off_title))
1722                    .setContentText(mContext.getText(R.string.notifications_off_text))
1723                    .setSmallIcon(R.drawable.ic_notification_dnd)
1724                    .setOngoing(true)
1725                    .getNotification();
1726
1727                final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd",
1728                        dndNotification);
1729                iconView.setImageResource(R.drawable.ic_notification_dnd);
1730                iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1731                iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0);
1732
1733                mNotificationDNDDummyEntry = new NotificationData.Entry(
1734                        null,
1735                        new StatusBarNotification("", 0, "", 0, 0, Notification.PRIORITY_MAX, dndNotification),
1736                        iconView);
1737
1738                mIconLayout.addView(iconView, params);
1739            }
1740
1741            return;
1742        } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) {
1743            // if icons are disabled but we're not in DND mode, this is probably Setup and we should
1744            // just leave the area totally empty
1745            return;
1746        }
1747
1748        int N = mNotificationData.size();
1749
1750        if (DEBUG) {
1751            Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
1752        }
1753
1754        ArrayList<View> toShow = new ArrayList<View>();
1755
1756        // Extra Special Icons
1757        // The IME switcher and compatibility mode icons take the place of notifications. You didn't
1758        // need to see all those new emails, did you?
1759        int maxNotificationIconsCount = mMaxNotificationIcons;
1760        if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount --;
1761        if (mCompatModeButton.getVisibility()        != View.GONE) maxNotificationIconsCount --;
1762
1763        for (int i=0; i< maxNotificationIconsCount; i++) {
1764            if (i>=N) break;
1765            toShow.add(mNotificationData.get(N-i-1).icon);
1766        }
1767
1768        ArrayList<View> toRemove = new ArrayList<View>();
1769        for (int i=0; i<mIconLayout.getChildCount(); i++) {
1770            View child = mIconLayout.getChildAt(i);
1771            if (!toShow.contains(child)) {
1772                toRemove.add(child);
1773            }
1774        }
1775
1776        for (View remove : toRemove) {
1777            mIconLayout.removeView(remove);
1778        }
1779
1780        for (int i=0; i<toShow.size(); i++) {
1781            View v = toShow.get(i);
1782            v.setPadding(mIconHPadding, 0, mIconHPadding, 0);
1783            if (v.getParent() == null) {
1784                mIconLayout.addView(v, i, params);
1785            }
1786        }
1787    }
1788
1789    private void loadNotificationPanel() {
1790        int N = mNotificationData.size();
1791
1792        ArrayList<View> toShow = new ArrayList<View>();
1793
1794        for (int i=0; i<N; i++) {
1795            View row = mNotificationData.get(N-i-1).row;
1796            toShow.add(row);
1797        }
1798
1799        ArrayList<View> toRemove = new ArrayList<View>();
1800        for (int i=0; i<mPile.getChildCount(); i++) {
1801            View child = mPile.getChildAt(i);
1802            if (!toShow.contains(child)) {
1803                toRemove.add(child);
1804            }
1805        }
1806
1807        for (View remove : toRemove) {
1808            mPile.removeView(remove);
1809        }
1810
1811        for (int i=0; i<toShow.size(); i++) {
1812            View v = toShow.get(i);
1813            if (v.getParent() == null) {
1814                mPile.addView(v, N-1-i); // the notification panel has newest at the bottom
1815            }
1816        }
1817
1818        mNotificationPanel.setNotificationCount(N);
1819    }
1820
1821    void workAroundBadLayerDrawableOpacity(View v) {
1822        Drawable bgd = v.getBackground();
1823        if (!(bgd instanceof LayerDrawable)) return;
1824
1825        LayerDrawable d = (LayerDrawable) bgd;
1826        v.setBackgroundDrawable(null);
1827        d.setOpacity(PixelFormat.TRANSLUCENT);
1828        v.setBackgroundDrawable(d);
1829    }
1830
1831    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
1832        StatusBarNotification sbn = entry.notification;
1833        RemoteViews remoteViews = sbn.notification.contentView;
1834        if (remoteViews == null) {
1835            return false;
1836        }
1837
1838        // create the row view
1839        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
1840                Context.LAYOUT_INFLATER_SERVICE);
1841        View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
1842        workAroundBadLayerDrawableOpacity(row);
1843        View vetoButton = updateNotificationVetoButton(row, entry.notification);
1844        vetoButton.setContentDescription(mContext.getString(
1845                R.string.accessibility_remove_notification));
1846
1847        // NB: the large icon is now handled entirely by the template
1848
1849        // bind the click event to the content area
1850        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
1851        // XXX: update to allow controls within notification views
1852        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
1853//        content.setOnFocusChangeListener(mFocusChangeListener);
1854        PendingIntent contentIntent = sbn.notification.contentIntent;
1855        if (contentIntent != null) {
1856            final View.OnClickListener listener = new NotificationClicker(
1857                    contentIntent, sbn.pkg, sbn.tag, sbn.id);
1858            content.setOnClickListener(listener);
1859        } else {
1860            content.setOnClickListener(null);
1861        }
1862
1863        View expanded = null;
1864        Exception exception = null;
1865        try {
1866            expanded = remoteViews.apply(mContext, content);
1867        }
1868        catch (RuntimeException e) {
1869            exception = e;
1870        }
1871        if (expanded == null) {
1872            final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
1873            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
1874            return false;
1875        } else {
1876            content.addView(expanded);
1877            row.setDrawingCacheEnabled(true);
1878        }
1879
1880        applyLegacyRowBackground(sbn, content);
1881
1882        entry.row = row;
1883        entry.content = content;
1884        entry.expanded = expanded;
1885
1886        return true;
1887    }
1888
1889    public void clearAll() {
1890        try {
1891            mBarService.onClearAllNotifications();
1892        } catch (RemoteException ex) {
1893            // system process is dead if we're here.
1894        }
1895        animateCollapse();
1896        visibilityChanged(false);
1897    }
1898
1899    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1900        public void onReceive(Context context, Intent intent) {
1901            String action = intent.getAction();
1902            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
1903                || Intent.ACTION_SCREEN_OFF.equals(action)) {
1904                boolean excludeRecents = false;
1905                if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
1906                    String reason = intent.getStringExtra("reason");
1907                    if (reason != null) {
1908                        excludeRecents = reason.equals("recentapps");
1909                    }
1910                }
1911                if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1912                    // If we're turning the screen off, we want to hide the
1913                    // recents panel with no animation
1914                    // TODO: hide other things, like the notification tray,
1915                    // with no animation as well
1916                    mRecentsPanel.show(false, false);
1917                    excludeRecents = true;
1918                }
1919                animateCollapse(excludeRecents);
1920            }
1921        }
1922    };
1923
1924    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1925        pw.print("mDisabled=0x");
1926        pw.println(Integer.toHexString(mDisabled));
1927        pw.println("mNetworkController:");
1928        mNetworkController.dump(fd, pw, args);
1929    }
1930
1931}
1932
1933
1934