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