TabletStatusBar.java revision 2992ea782fa61780d8e0de7a36a2a84622f8694b
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;
22import java.util.Map;
23import java.util.IdentityHashMap;
24
25import android.animation.LayoutTransition;
26import android.animation.ObjectAnimator;
27import android.animation.AnimatorSet;
28import android.app.ActivityManagerNative;
29import android.app.PendingIntent;
30import android.app.Notification;
31import android.app.StatusBarManager;
32import android.content.Context;
33import android.content.Intent;
34import android.content.res.Configuration;
35import android.content.res.Resources;
36import android.inputmethodservice.InputMethodService;
37import android.graphics.PixelFormat;
38import android.graphics.Rect;
39import android.graphics.drawable.Drawable;
40import android.graphics.drawable.LayerDrawable;
41import android.os.Handler;
42import android.os.IBinder;
43import android.os.Message;
44import android.os.RemoteException;
45import android.os.ServiceManager;
46import android.text.TextUtils;
47import android.util.Slog;
48import android.view.animation.Animation;
49import android.view.animation.AnimationUtils;
50import android.view.Gravity;
51import android.view.IWindowManager;
52import android.view.KeyEvent;
53import android.view.LayoutInflater;
54import android.view.MotionEvent;
55import android.view.VelocityTracker;
56import android.view.View;
57import android.view.ViewConfiguration;
58import android.view.ViewGroup;
59import android.view.WindowManager;
60import android.view.WindowManagerImpl;
61import android.widget.FrameLayout;
62import android.widget.ImageView;
63import android.widget.LinearLayout;
64import android.widget.RemoteViews;
65import android.widget.ScrollView;
66import android.widget.TextSwitcher;
67import android.widget.TextView;
68
69import com.android.internal.statusbar.StatusBarIcon;
70import com.android.internal.statusbar.StatusBarNotification;
71
72import com.android.systemui.R;
73import com.android.systemui.statusbar.*;
74import com.android.systemui.statusbar.policy.BatteryController;
75import com.android.systemui.statusbar.policy.NetworkController;
76import com.android.systemui.recent.RecentApplicationsActivity;
77
78public class TabletStatusBar extends StatusBar implements
79        HeightReceiver.OnBarHeightChangedListener,
80        InputMethodsPanel.OnHardKeyboardEnabledChangeListener {
81    public static final boolean DEBUG = false;
82    public static final String TAG = "TabletStatusBar";
83
84    public static final int MAX_NOTIFICATION_ICONS = 5;
85    // IME switcher icon is big and occupy width of two icons
86    public static final int MAX_NOTIFICATION_ICONS_IME_BUTTON_VISIBLE = MAX_NOTIFICATION_ICONS - 1;
87
88    public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
89    public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
90    public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002;
91    public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003;
92    public static final int MSG_OPEN_RECENTS_PANEL = 1020;
93    public static final int MSG_CLOSE_RECENTS_PANEL = 1021;
94    public static final int MSG_SHOW_CHROME = 1030;
95    public static final int MSG_HIDE_CHROME = 1031;
96    public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040;
97    public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041;
98
99    // Fitts' Law assistance for LatinIME; TODO: replace with a more general approach
100    private static final boolean FAKE_SPACE_BAR = true;
101
102    // The height of the bar, as definied by the build.  It may be taller if we're plugged
103    // into hdmi.
104    int mNaturalBarHeight = -1;
105    int mIconSize = -1;
106    int mIconHPadding = -1;
107
108    H mHandler = new H();
109
110    IWindowManager mWindowManager;
111
112    // tracking all current notifications
113    private NotificationData mNotns = new NotificationData();
114
115    TabletStatusBarView mStatusBarView;
116    View mNotificationArea;
117    View mNotificationTrigger;
118    NotificationIconArea mNotificationIconArea;
119    View mNavigationArea;
120
121    ImageView mBackButton;
122    View mHomeButton;
123    View mMenuButton;
124    View mRecentButton;
125
126    ViewGroup mNotificationAndImeArea;
127    InputMethodButton mInputMethodSwitchButton;
128
129    NotificationPanel mNotificationPanel;
130    NotificationPeekPanel mNotificationPeekWindow;
131    ViewGroup mNotificationPeekRow;
132    int mNotificationPeekIndex;
133    IBinder mNotificationPeekKey;
134    LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight;
135
136    int mNotificationPeekTapDuration;
137    int mNotificationFlingVelocity;
138
139    ViewGroup mPile;
140
141    HeightReceiver mHeightReceiver;
142    BatteryController mBatteryController;
143    NetworkController mNetworkController;
144
145    View mBarContents;
146
147    // hide system chrome ("lights out") support
148    View mShadow;
149
150    NotificationIconArea.IconLayout mIconLayout;
151
152    TabletTicker mTicker;
153
154    View mFakeSpaceBar;
155    KeyEvent mSpaceBarKeyEvent = null;
156
157    // for disabling the status bar
158    int mDisabled = 0;
159
160    boolean mNotificationsOn = true;
161    private RecentAppsPanel mRecentsPanel;
162    private InputMethodsPanel mInputMethodsPanel;
163
164    public Context getContext() { return mContext; }
165
166    protected void addPanelWindows() {
167        final Context context = mContext;
168
169        // Notification Panel
170        mNotificationPanel = (NotificationPanel)View.inflate(context,
171                R.layout.status_bar_notification_panel, null);
172        mNotificationPanel.show(false, false);
173        mNotificationPanel.setOnTouchListener(
174                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel));
175
176        // the battery and network icons
177        mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery));
178        mBatteryController.addLabelView(
179                (TextView)mNotificationPanel.findViewById(R.id.battery_text));
180        mNetworkController.addCombinedSignalIconView(
181                (ImageView)mNotificationPanel.findViewById(R.id.network_signal));
182        mNetworkController.addDataTypeIconView(
183                (ImageView)mNotificationPanel.findViewById(R.id.network_type));
184        mNetworkController.addLabelView(
185                (TextView)mNotificationPanel.findViewById(R.id.network_text));
186        mNetworkController.addLabelView(
187                (TextView)mBarContents.findViewById(R.id.network_text));
188
189        mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
190
191        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
192                512, // ViewGroup.LayoutParams.MATCH_PARENT,
193                ViewGroup.LayoutParams.MATCH_PARENT,
194                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
195                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
196                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
197                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
198                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
199                PixelFormat.TRANSLUCENT);
200        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
201        lp.setTitle("NotificationPanel");
202        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
203                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
204        lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation
205//        lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
206
207        WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
208
209        // Notification preview window
210        mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context,
211                R.layout.status_bar_notification_peek, null);
212        mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content);
213        mNotificationPeekWindow.setVisibility(View.GONE);
214        mNotificationPeekWindow.setOnTouchListener(
215                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow));
216        mNotificationPeekScrubRight = new LayoutTransition();
217        mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING,
218                ObjectAnimator.ofInt(null, "left", -512, 0));
219        mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING,
220                ObjectAnimator.ofInt(null, "left", -512, 0));
221        mNotificationPeekScrubRight.setDuration(500);
222
223        mNotificationPeekScrubLeft = new LayoutTransition();
224        mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING,
225                ObjectAnimator.ofInt(null, "left", 512, 0));
226        mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING,
227                ObjectAnimator.ofInt(null, "left", 512, 0));
228        mNotificationPeekScrubLeft.setDuration(500);
229
230        // XXX: setIgnoreChildren?
231        lp = new WindowManager.LayoutParams(
232                512, // ViewGroup.LayoutParams.WRAP_CONTENT,
233                ViewGroup.LayoutParams.WRAP_CONTENT,
234                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
235                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
236                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
237                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
238                PixelFormat.TRANSLUCENT);
239        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
240        lp.setTitle("NotificationPeekWindow");
241        lp.windowAnimations = com.android.internal.R.style.Animation_Toast;
242
243        WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp);
244
245        // Recents Panel
246        mRecentsPanel = (RecentAppsPanel) View.inflate(context,
247                R.layout.status_bar_recent_panel, null);
248        mRecentsPanel.setVisibility(View.GONE);
249        mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
250                mRecentsPanel));
251        mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel);
252
253        lp = new WindowManager.LayoutParams(
254                ViewGroup.LayoutParams.WRAP_CONTENT,
255                ViewGroup.LayoutParams.WRAP_CONTENT,
256                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
257                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
258                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
259                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
260                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
261                PixelFormat.TRANSLUCENT);
262        lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
263        lp.setTitle("RecentsPanel");
264        lp.windowAnimations = R.style.Animation_RecentPanel;
265
266        WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
267        mRecentsPanel.setBar(this);
268
269        // Input methods Panel
270        mInputMethodsPanel = (InputMethodsPanel) View.inflate(context,
271                R.layout.status_bar_input_methods_panel, null);
272        mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this);
273        mInputMethodsPanel.setVisibility(View.GONE);
274        mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener(
275                MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel));
276        mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton);
277        mStatusBarView.setIgnoreChildren(3, mInputMethodSwitchButton, mInputMethodsPanel);
278        lp = new WindowManager.LayoutParams(
279                ViewGroup.LayoutParams.WRAP_CONTENT,
280                ViewGroup.LayoutParams.WRAP_CONTENT,
281                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
282                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
283                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
284                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
285                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
286                PixelFormat.TRANSLUCENT);
287        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
288        lp.setTitle("InputMethodsPanel");
289        lp.windowAnimations = R.style.Animation_RecentPanel;
290
291        WindowManagerImpl.getDefault().addView(mInputMethodsPanel, lp);
292    }
293
294    @Override
295    public void start() {
296        super.start(); // will add the main bar view
297    }
298
299    @Override
300    protected void onConfigurationChanged(Configuration newConfig) {
301        loadDimens();
302    }
303
304    protected void loadDimens() {
305        final Resources res = mContext.getResources();
306
307        mNaturalBarHeight = res.getDimensionPixelSize(
308                com.android.internal.R.dimen.status_bar_height);
309
310        int newIconSize = res.getDimensionPixelSize(
311            com.android.internal.R.dimen.status_bar_icon_size);
312        int newIconHPadding = res.getDimensionPixelSize(
313            R.dimen.status_bar_icon_padding);
314
315        if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) {
316//            Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding);
317            mIconHPadding = newIconHPadding;
318            mIconSize = newIconSize;
319            reloadAllNotificationIcons(); // reload the tray
320        }
321    }
322
323    protected View makeStatusBarView() {
324        final Context context = mContext;
325
326        mWindowManager = IWindowManager.Stub.asInterface(
327                ServiceManager.getService(Context.WINDOW_SERVICE));
328
329        // This guy will listen for HDMI plugged broadcasts so we can resize the
330        // status bar as appropriate.
331        mHeightReceiver = new HeightReceiver(mContext);
332        mHeightReceiver.registerReceiver();
333        loadDimens();
334
335        final TabletStatusBarView sb = (TabletStatusBarView)View.inflate(
336                context, R.layout.status_bar, null);
337        mStatusBarView = sb;
338
339        sb.setHandler(mHandler);
340
341        mBarContents = sb.findViewById(R.id.bar_contents);
342
343        // the whole right-hand side of the bar
344        mNotificationArea = sb.findViewById(R.id.notificationArea);
345
346        // the button to open the notification area
347        mNotificationTrigger = sb.findViewById(R.id.notificationTrigger);
348        mNotificationTrigger.setOnClickListener(mOnClickListener);
349
350        // the more notifications icon
351        mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
352
353        // where the icons go
354        mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
355        mIconLayout.setOnTouchListener(new NotificationIconTouchListener());
356
357        ViewConfiguration vc = ViewConfiguration.get(context);
358        mNotificationPeekTapDuration = vc.getTapTimeout();
359        mNotificationFlingVelocity = 300; // px/s
360
361        mTicker = new TabletTicker(this);
362
363        // The icons
364        mBatteryController = new BatteryController(mContext);
365        mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
366        mNetworkController = new NetworkController(mContext);
367        mNetworkController.addCombinedSignalIconView(
368                (ImageView)sb.findViewById(R.id.network_signal));
369        mNetworkController.addDataTypeIconView(
370                (ImageView)sb.findViewById(R.id.network_type));
371
372        // The navigation buttons
373        mBackButton = (ImageView)sb.findViewById(R.id.back);
374        mNavigationArea = sb.findViewById(R.id.navigationArea);
375        mHomeButton = mNavigationArea.findViewById(R.id.home);
376        mMenuButton = mNavigationArea.findViewById(R.id.menu);
377        mRecentButton = mNavigationArea.findViewById(R.id.recent_apps);
378        mRecentButton.setOnClickListener(mOnClickListener);
379
380        // The bar contents buttons
381        mNotificationAndImeArea = (ViewGroup)sb.findViewById(R.id.notificationAndImeArea);
382        mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton);
383        // Overwrite the lister
384        mInputMethodSwitchButton.setOnClickListener(mOnClickListener);
385
386        // for redirecting errant bar taps to the IME
387        mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar);
388
389        // "shadows" of the status bar features, for lights-out mode
390        mShadow = sb.findViewById(R.id.bar_shadow);
391        mShadow.setOnTouchListener(
392            new View.OnTouchListener() {
393                public boolean onTouch(View v, MotionEvent ev) {
394                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
395                        // even though setting the systemUI visibility below will turn these views
396                        // on, we need them to come up faster so that they can catch this motion
397                        // event
398                        mShadow.setVisibility(View.GONE);
399                        mBarContents.setVisibility(View.VISIBLE);
400
401                        try {
402                            mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
403                        } catch (RemoteException ex) {
404                            // system process dead
405                        }
406                    }
407                    return false;
408                }
409            });
410
411        // tuning parameters
412        final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 600;
413        final int LIGHTS_GOING_OUT_SHADOW_DURATION = 1000;
414        final int LIGHTS_GOING_OUT_SHADOW_DELAY    = 500;
415
416        final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200;
417//        final int LIGHTS_COMING_UP_SYSBAR_DELAY    = 50;
418        final int LIGHTS_COMING_UP_SHADOW_DURATION = 0;
419
420        LayoutTransition xition = new LayoutTransition();
421        xition.setAnimator(LayoutTransition.APPEARING,
422               ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f));
423        xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION);
424        xition.setStartDelay(LayoutTransition.APPEARING, 0);
425        xition.setAnimator(LayoutTransition.DISAPPEARING,
426               ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
427        xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION);
428        xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
429        ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition);
430
431        xition = new LayoutTransition();
432        xition.setAnimator(LayoutTransition.APPEARING,
433               ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
434        xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION);
435        xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY);
436        xition.setAnimator(LayoutTransition.DISAPPEARING,
437               ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
438        xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION);
439        xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
440        ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition);
441
442        // set the initial view visibility
443        setAreThereNotifications();
444
445        // Add the windows
446        addPanelWindows();
447
448        mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
449        mPile.removeAllViews();
450
451        ScrollView scroller = (ScrollView)mPile.getParent();
452        scroller.setFillViewport(true);
453
454        mHeightReceiver.addOnBarHeightChangedListener(this);
455
456        return sb;
457    }
458
459    public int getStatusBarHeight() {
460        return mHeightReceiver.getHeight();
461    }
462
463    protected int getStatusBarGravity() {
464        return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
465    }
466
467    public void onBarHeightChanged(int height) {
468        final WindowManager.LayoutParams lp
469                = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams();
470        if (lp == null) {
471            // haven't been added yet
472            return;
473        }
474        if (lp.height != height) {
475            lp.height = height;
476            final WindowManager wm = WindowManagerImpl.getDefault();
477            wm.updateViewLayout(mStatusBarView, lp);
478        }
479    }
480
481    private class H extends Handler {
482        public void handleMessage(Message m) {
483            switch (m.what) {
484                case MSG_OPEN_NOTIFICATION_PEEK:
485                    if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1);
486                    if (m.arg1 >= 0) {
487                        final int N = mNotns.size();
488                        if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
489                            NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex);
490                            entry.icon.setBackgroundColor(0);
491                            mNotificationPeekIndex = -1;
492                            mNotificationPeekKey = null;
493                        }
494
495                        final int peekIndex = m.arg1;
496                        if (peekIndex < N) {
497                            //Slog.d(TAG, "loading peek: " + peekIndex);
498                            NotificationData.Entry entry = mNotns.get(N-1-peekIndex);
499                            NotificationData.Entry copy = new NotificationData.Entry(
500                                    entry.key,
501                                    entry.notification,
502                                    entry.icon);
503                            inflateViews(copy, mNotificationPeekRow);
504
505                            entry.icon.setBackgroundColor(0x20FFFFFF);
506
507//                          mNotificationPeekRow.setLayoutTransition(
508//                              peekIndex < mNotificationPeekIndex
509//                                  ? mNotificationPeekScrubLeft
510//                                  : mNotificationPeekScrubRight);
511
512                            mNotificationPeekRow.removeAllViews();
513                            mNotificationPeekRow.addView(copy.row);
514
515                            mNotificationPeekWindow.setVisibility(View.VISIBLE);
516                            mNotificationPanel.show(false, true);
517
518                            mNotificationPeekIndex = peekIndex;
519                            mNotificationPeekKey = entry.key;
520                        }
521                    }
522                    break;
523                case MSG_CLOSE_NOTIFICATION_PEEK:
524                    if (DEBUG) Slog.d(TAG, "closing notification peek window");
525                    mNotificationPeekWindow.setVisibility(View.GONE);
526                    mNotificationPeekRow.removeAllViews();
527                    final int N = mNotns.size();
528                    if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
529                        NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex);
530                        entry.icon.setBackgroundColor(0);
531                    }
532
533                    mNotificationPeekIndex = -1;
534                    mNotificationPeekKey = null;
535                    break;
536                case MSG_OPEN_NOTIFICATION_PANEL:
537                    if (DEBUG) Slog.d(TAG, "opening notifications panel");
538                    if (!mNotificationPanel.isShowing()) {
539                        mNotificationPeekWindow.setVisibility(View.GONE);
540                        mNotificationPanel.show(true, true);
541                        mNotificationArea.setVisibility(View.GONE);
542                        mTicker.halt();
543                    }
544                    break;
545                case MSG_CLOSE_NOTIFICATION_PANEL:
546                    if (DEBUG) Slog.d(TAG, "closing notifications panel");
547                    if (mNotificationPanel.isShowing()) {
548                        mNotificationPanel.show(false, true);
549                        mNotificationArea.setVisibility(View.VISIBLE);
550                    }
551                    break;
552                case MSG_OPEN_RECENTS_PANEL:
553                    if (DEBUG) Slog.d(TAG, "opening recents panel");
554                    if (mRecentsPanel != null) {
555                        mRecentsPanel.setVisibility(View.VISIBLE);
556                        mRecentsPanel.show(true, true);
557                    }
558                    break;
559                case MSG_CLOSE_RECENTS_PANEL:
560                    if (DEBUG) Slog.d(TAG, "closing recents panel");
561                    if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
562                        mRecentsPanel.show(false, true);
563                    }
564                    break;
565                case MSG_OPEN_INPUT_METHODS_PANEL:
566                    if (DEBUG) Slog.d(TAG, "opening input methods panel");
567                    if (mInputMethodsPanel != null) mInputMethodsPanel.setVisibility(View.VISIBLE);
568                    break;
569                case MSG_CLOSE_INPUT_METHODS_PANEL:
570                    if (DEBUG) Slog.d(TAG, "closing input methods panel");
571                    if (mInputMethodsPanel != null) mInputMethodsPanel.setVisibility(View.GONE);
572                    break;
573                case MSG_SHOW_CHROME:
574                    if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)");
575                    mBarContents.setVisibility(View.VISIBLE);
576                    mShadow.setVisibility(View.GONE);
577                    notifyLightsChanged(true);
578                    break;
579                case MSG_HIDE_CHROME:
580                    if (DEBUG) Slog.d(TAG, "showing shadows (lights out)");
581                    animateCollapse();
582                    mBarContents.setVisibility(View.GONE);
583                    mShadow.setVisibility(View.VISIBLE);
584                    notifyLightsChanged(false);
585                    break;
586            }
587        }
588    }
589
590    private void notifyLightsChanged(boolean shown) {
591        try {
592            Slog.d(TAG, "lights " + (shown?"on":"out"));
593            mWindowManager.statusBarVisibilityChanged(
594                    shown ? View.STATUS_BAR_VISIBLE : View.STATUS_BAR_HIDDEN);
595        } catch (RemoteException ex) {
596        }
597    }
598
599    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
600        if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon);
601    }
602
603    public void updateIcon(String slot, int index, int viewIndex,
604            StatusBarIcon old, StatusBarIcon icon) {
605        if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon);
606    }
607
608    public void removeIcon(String slot, int index, int viewIndex) {
609        if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")");
610    }
611
612    public void addNotification(IBinder key, StatusBarNotification notification) {
613        if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
614        addNotificationViews(key, notification);
615
616        final boolean immersive = isImmersive();
617        if (false && immersive) {
618            // TODO: immersive mode popups for tablet
619        } else if (notification.notification.fullScreenIntent != null) {
620            // not immersive & a full-screen alert should be shown
621            Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;"
622                    + " sending fullScreenIntent");
623            try {
624                notification.notification.fullScreenIntent.send();
625            } catch (PendingIntent.CanceledException e) {
626            }
627        } else {
628            tick(key, notification, true);
629        }
630
631        setAreThereNotifications();
632    }
633
634    public void updateNotification(IBinder key, StatusBarNotification notification) {
635        if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ") // TODO");
636
637        final NotificationData.Entry oldEntry = mNotns.findByKey(key);
638        if (oldEntry == null) {
639            Slog.w(TAG, "updateNotification for unknown key: " + key);
640            return;
641        }
642
643        final StatusBarNotification oldNotification = oldEntry.notification;
644        final RemoteViews oldContentView = oldNotification.notification.contentView;
645
646        final RemoteViews contentView = notification.notification.contentView;
647
648        if (DEBUG) {
649            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
650                    + " ongoing=" + oldNotification.isOngoing()
651                    + " expanded=" + oldEntry.expanded
652                    + " contentView=" + oldContentView);
653            Slog.d(TAG, "new notification: when=" + notification.notification.when
654                    + " ongoing=" + oldNotification.isOngoing()
655                    + " contentView=" + contentView);
656        }
657
658        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
659        // didn't change.
660        boolean contentsUnchanged = oldEntry.expanded != null
661                && contentView != null && oldContentView != null
662                && contentView.getPackage() != null
663                && oldContentView.getPackage() != null
664                && oldContentView.getPackage().equals(contentView.getPackage())
665                && oldContentView.getLayoutId() == contentView.getLayoutId();
666        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
667        boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
668                && notification.isOngoing() == oldNotification.isOngoing();
669        boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1;
670        if (contentsUnchanged && (orderUnchanged || isLastAnyway)) {
671            if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
672            oldEntry.notification = notification;
673            try {
674                // Reapply the RemoteViews
675                contentView.reapply(mContext, oldEntry.content);
676                // update the contentIntent
677                final PendingIntent contentIntent = notification.notification.contentIntent;
678                if (contentIntent != null) {
679                    oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent,
680                                notification.pkg, notification.tag, notification.id));
681                } else {
682                    oldEntry.content.setOnClickListener(null);
683                }
684                // Update the icon.
685                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
686                        notification.notification.icon, notification.notification.iconLevel,
687                        notification.notification.number);
688                if (!oldEntry.icon.set(ic)) {
689                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
690                    return;
691                }
692                // Update the large icon
693                if (notification.notification.largeIcon != null) {
694                    oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon);
695                } else {
696                    oldEntry.largeIcon.getLayoutParams().width = 0;
697                    oldEntry.largeIcon.setVisibility(View.INVISIBLE);
698                }
699
700                if (key == mNotificationPeekKey) {
701                    // must update the peek window
702                    Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
703                    peekMsg.arg1 = mNotificationPeekIndex;
704                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
705                    mHandler.sendMessage(peekMsg);
706                }
707            }
708            catch (RuntimeException e) {
709                // It failed to add cleanly.  Log, and remove the view from the panel.
710                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
711                removeNotificationViews(key);
712                addNotificationViews(key, notification);
713            }
714        } else {
715            if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
716            removeNotificationViews(key);
717            addNotificationViews(key, notification);
718        }
719        // fullScreenIntent doesn't happen on updates.  You need to clear & repost a new
720        // notification.
721        final boolean immersive = isImmersive();
722        if (false && immersive) {
723            // TODO: immersive mode
724        } else {
725            tick(key, notification, false);
726        }
727
728        setAreThereNotifications();
729    }
730
731    public void removeNotification(IBinder key) {
732        if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO");
733        removeNotificationViews(key);
734        mTicker.remove(key);
735        setAreThereNotifications();
736    }
737
738    public void showClock(boolean show) {
739        View clock = mBarContents.findViewById(R.id.clock);
740        View network_text = mBarContents.findViewById(R.id.network_text);
741        if (clock != null) {
742            clock.setVisibility(show ? View.VISIBLE : View.GONE);
743        }
744        if (network_text != null) {
745            network_text.setVisibility((!show) ? View.VISIBLE : View.GONE);
746        }
747    }
748
749    public void disable(int state) {
750        int old = mDisabled;
751        int diff = state ^ old;
752        mDisabled = state;
753
754        // act accordingly
755        if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
756            boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
757            Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes"));
758            showClock(show);
759        }
760        if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
761            boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0;
762            Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes"));
763            mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE);
764        }
765        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
766            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
767                Slog.i(TAG, "DISABLE_EXPAND: yes");
768                animateCollapse();
769            }
770        }
771        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
772            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
773                Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
774                // synchronize with current shadow state
775                mNotificationIconArea.setVisibility(View.GONE);
776                mTicker.halt();
777            } else {
778                Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no");
779                // synchronize with current shadow state
780                mNotificationIconArea.setVisibility(View.VISIBLE);
781            }
782        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
783            if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
784                mTicker.halt();
785            }
786        }
787        if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) {
788            if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) {
789                Slog.i(TAG, "DISABLE_NAVIGATION: yes");
790                mNavigationArea.setVisibility(View.GONE);
791                mInputMethodSwitchButton.setScreenLocked(true);
792            } else {
793                Slog.i(TAG, "DISABLE_NAVIGATION: no");
794                mNavigationArea.setVisibility(View.VISIBLE);
795                mInputMethodSwitchButton.setScreenLocked(false);
796            }
797        }
798        if ((diff & StatusBarManager.DISABLE_BACK) != 0) {
799            if ((state & StatusBarManager.DISABLE_BACK) != 0) {
800                Slog.i(TAG, "DISABLE_BACK: yes");
801                mBackButton.setVisibility(View.INVISIBLE);
802                mInputMethodSwitchButton.setScreenLocked(true);
803            } else {
804                Slog.i(TAG, "DISABLE_BACK: no");
805                mBackButton.setVisibility(View.VISIBLE);
806                mInputMethodSwitchButton.setScreenLocked(false);
807            }
808        }
809
810    }
811
812    private boolean hasTicker(Notification n) {
813        return n.tickerView != null || !TextUtils.isEmpty(n.tickerText);
814    }
815
816    private void tick(IBinder key, StatusBarNotification n, boolean firstTime) {
817        // Don't show the ticker when the windowshade is open.
818        if (mNotificationPanel.isShowing()) {
819            return;
820        }
821        // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification
822        // if it's a new notification.
823        if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) {
824            return;
825        }
826        // Show the ticker if one is requested. Also don't do this
827        // until status bar window is attached to the window manager,
828        // because...  well, what's the point otherwise?  And trying to
829        // run a ticker without being attached will crash!
830        if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
831            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
832                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
833                mTicker.add(key, n);
834                mNotificationAndImeArea.setVisibility(View.GONE);
835            }
836        }
837    }
838
839    // called by TabletTicker when it's done with all queued ticks
840    public void doneTicking() {
841        mNotificationAndImeArea.setVisibility(View.VISIBLE);
842    }
843
844    public void animateExpand() {
845        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
846        mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
847    }
848
849    public void animateCollapse() {
850        mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
851        mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
852        mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
853        mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
854        mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL);
855        mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL);
856    }
857
858    // called by StatusBar
859    @Override
860    public void setLightsOn(boolean on) {
861        // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app
862        // that can't handle lights-out mode.
863        if (mMenuButton.getVisibility() == View.VISIBLE) {
864            on = true;
865        }
866        mHandler.removeMessages(MSG_HIDE_CHROME);
867        mHandler.removeMessages(MSG_SHOW_CHROME);
868        mHandler.sendEmptyMessage(on ? MSG_SHOW_CHROME : MSG_HIDE_CHROME);
869    }
870
871    public void setMenuKeyVisible(boolean visible) {
872        if (DEBUG) {
873            Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button");
874        }
875        mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE);
876
877        // See above re: lights-out policy for legacy apps.
878        if (visible) setLightsOn(true);
879    }
880
881    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
882        mInputMethodSwitchButton.setImeWindowStatus(token,
883                (vis & InputMethodService.IME_ACTIVE) != 0);
884        updateNotificationIcons();
885        mInputMethodsPanel.setImeToken(token);
886        int res;
887        switch (backDisposition) {
888            case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
889                res = R.drawable.ic_sysbar_back;
890                break;
891            case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
892                res = R.drawable.ic_sysbar_back_ime;
893                break;
894            case InputMethodService.BACK_DISPOSITION_DEFAULT:
895            default:
896                if ((vis & InputMethodService.IME_VISIBLE) != 0) {
897                    res = R.drawable.ic_sysbar_back_ime;
898                } else {
899                    res = R.drawable.ic_sysbar_back;
900                }
901                break;
902        }
903        mBackButton.setImageResource(res);
904        if (FAKE_SPACE_BAR) {
905            mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0)
906                    ? View.VISIBLE : View.GONE);
907        }
908    }
909
910    @Override
911    public void setHardKeyboardStatus(boolean available, boolean enabled) {
912        if (DEBUG) {
913            Slog.d(TAG, "Set hard keyboard status: available=" + available
914                    + ", enabled=" + enabled);
915        }
916        mInputMethodSwitchButton.setHardKeyboardStatus(available);
917        updateNotificationIcons();
918        mInputMethodsPanel.setHardKeyboardStatus(available, enabled);
919    }
920
921    @Override
922    public void onHardKeyboardEnabledChange(boolean enabled) {
923        try {
924            mBarService.setHardKeyboardEnabled(enabled);
925        } catch (RemoteException ex) {
926        }
927    }
928
929    private boolean isImmersive() {
930        try {
931            return ActivityManagerNative.getDefault().isTopActivityImmersive();
932            //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
933        } catch (RemoteException ex) {
934            // the end is nigh
935            return false;
936        }
937    }
938
939    private void setAreThereNotifications() {
940        final boolean hasClearable = mNotns.hasClearableItems();
941    }
942
943    /**
944     * Cancel this notification and tell the status bar service about the failure. Hold no locks.
945     */
946    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
947        removeNotification(key);
948        try {
949            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
950        } catch (RemoteException ex) {
951            // The end is nigh.
952        }
953    }
954
955    private void sendKey(KeyEvent key) {
956        try {
957            if (DEBUG) Slog.d(TAG, "injecting key event: " + key);
958            mWindowManager.injectInputEventNoWait(key);
959        } catch (RemoteException ex) {
960        }
961    }
962
963    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
964        public void onClick(View v) {
965            if (v == mNotificationTrigger) {
966                onClickNotificationTrigger();
967            } else if (v == mRecentButton) {
968                onClickRecentButton();
969            } else if (v == mInputMethodSwitchButton) {
970                onClickInputMethodSwitchButton();
971            }
972        }
973    };
974
975    public void onClickNotificationTrigger() {
976        if (DEBUG) Slog.d(TAG, "clicked notification icons; disabled=" + mDisabled);
977        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
978            if (!mNotificationsOn) {
979                mNotificationsOn = true;
980                mIconLayout.setVisibility(View.VISIBLE); // TODO: animation
981            } else {
982                int msg = !mNotificationPanel.isShowing()
983                    ? MSG_OPEN_NOTIFICATION_PANEL
984                    : MSG_CLOSE_NOTIFICATION_PANEL;
985                mHandler.removeMessages(msg);
986                mHandler.sendEmptyMessage(msg);
987            }
988        }
989    }
990
991    public void onClickRecentButton() {
992        if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled);
993        if (mRecentsPanel == null) {
994            Intent intent = new Intent();
995            intent.setClass(mContext, RecentApplicationsActivity.class);
996            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
997                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
998            mContext.startActivity(intent);
999        } else {
1000            if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
1001                int msg = (mRecentsPanel.getVisibility() == View.GONE)
1002                    ? MSG_OPEN_RECENTS_PANEL
1003                    : MSG_CLOSE_RECENTS_PANEL;
1004                mHandler.removeMessages(msg);
1005                mHandler.sendEmptyMessage(msg);
1006            }
1007        }
1008    }
1009
1010    public void onClickInputMethodSwitchButton() {
1011        if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled);
1012        int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ?
1013                MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL;
1014        mHandler.removeMessages(msg);
1015        mHandler.sendEmptyMessage(msg);
1016    }
1017
1018    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
1019        return new NotificationClicker(intent, pkg, tag, id);
1020    }
1021
1022    private class NotificationClicker implements View.OnClickListener {
1023        private PendingIntent mIntent;
1024        private String mPkg;
1025        private String mTag;
1026        private int mId;
1027
1028        NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
1029            mIntent = intent;
1030            mPkg = pkg;
1031            mTag = tag;
1032            mId = id;
1033        }
1034
1035        public void onClick(View v) {
1036            try {
1037                // The intent we are sending is for the application, which
1038                // won't have permission to immediately start an activity after
1039                // the user switches to home.  We know it is safe to do at this
1040                // point, so make sure new activity switches are now allowed.
1041                ActivityManagerNative.getDefault().resumeAppSwitches();
1042            } catch (RemoteException e) {
1043            }
1044
1045            if (mIntent != null) {
1046                int[] pos = new int[2];
1047                v.getLocationOnScreen(pos);
1048                Intent overlay = new Intent();
1049                overlay.setSourceBounds(
1050                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
1051                try {
1052                    mIntent.send(mContext, 0, overlay);
1053                } catch (PendingIntent.CanceledException e) {
1054                    // the stack trace isn't very helpful here.  Just log the exception message.
1055                    Slog.w(TAG, "Sending contentIntent failed: " + e);
1056                }
1057            }
1058
1059            try {
1060                mBarService.onNotificationClick(mPkg, mTag, mId);
1061            } catch (RemoteException ex) {
1062                // system process is dead if we're here.
1063            }
1064
1065            // close the shade if it was open
1066            animateCollapse();
1067
1068            // If this click was on the intruder alert, hide that instead
1069//            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
1070        }
1071    }
1072
1073    StatusBarNotification removeNotificationViews(IBinder key) {
1074        NotificationData.Entry entry = mNotns.remove(key);
1075        if (entry == null) {
1076            Slog.w(TAG, "removeNotification for unknown key: " + key);
1077            return null;
1078        }
1079        // Remove the expanded view.
1080        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
1081        if (rowParent != null) rowParent.removeView(entry.row);
1082
1083        if (key == mNotificationPeekKey) {
1084            // must close the peek as well, since it's gone
1085            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1086        }
1087        // Remove the icon.
1088//        ViewGroup iconParent = (ViewGroup)entry.icon.getParent();
1089//        if (iconParent != null) iconParent.removeView(entry.icon);
1090        updateNotificationIcons();
1091
1092        return entry.notification;
1093    }
1094
1095    private class NotificationIconTouchListener implements View.OnTouchListener {
1096        VelocityTracker mVT;
1097
1098        public NotificationIconTouchListener() {
1099        }
1100
1101        public boolean onTouch(View v, MotionEvent event) {
1102            boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
1103            boolean panelShowing = mNotificationPanel.isShowing();
1104            if (panelShowing) return false;
1105
1106            switch (event.getAction()) {
1107                case MotionEvent.ACTION_DOWN:
1108                    mVT = VelocityTracker.obtain();
1109
1110                    // fall through
1111                case MotionEvent.ACTION_OUTSIDE:
1112                case MotionEvent.ACTION_MOVE:
1113                    // peek and switch icons if necessary
1114                    int numIcons = mIconLayout.getChildCount();
1115                    int peekIndex = (int)((float)event.getX() * numIcons / mIconLayout.getWidth());
1116                    if (peekIndex > numIcons - 1) peekIndex = numIcons - 1;
1117                    else if (peekIndex < 0) peekIndex = 0;
1118
1119                    if (!peeking || mNotificationPeekIndex != peekIndex) {
1120                        if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex);
1121                        Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
1122                        peekMsg.arg1 = peekIndex;
1123
1124                        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1125
1126                        // no delay if we're scrubbing left-right
1127                        mHandler.sendMessage(peekMsg);
1128                    }
1129
1130                    // check for fling
1131                    if (mVT != null) {
1132                        mVT.addMovement(event);
1133                        mVT.computeCurrentVelocity(1000);
1134                        // require a little more oomph once we're already in peekaboo mode
1135                        if (!panelShowing && (
1136                               (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
1137                            || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
1138                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1139                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
1140                            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1141                            mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
1142                        }
1143                    }
1144                    return true;
1145                case MotionEvent.ACTION_UP:
1146                case MotionEvent.ACTION_CANCEL:
1147                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1148                    if (peeking) {
1149                        mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 5000);
1150                    }
1151                    mVT.recycle();
1152                    mVT = null;
1153                    return true;
1154            }
1155            return false;
1156        }
1157    }
1158
1159    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
1160        if (DEBUG) {
1161            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
1162        }
1163        // Construct the icon.
1164        final StatusBarIconView iconView = new StatusBarIconView(mContext,
1165                notification.pkg + "/0x" + Integer.toHexString(notification.id));
1166        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1167
1168        final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
1169                    notification.notification.icon,
1170                    notification.notification.iconLevel,
1171                    notification.notification.number);
1172        if (!iconView.set(ic)) {
1173            handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
1174            return null;
1175        }
1176        // Construct the expanded view.
1177        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
1178        if (!inflateViews(entry, mPile)) {
1179            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
1180                    + notification);
1181            return null;
1182        }
1183
1184        // Add the icon.
1185        mNotns.add(entry);
1186        updateNotificationIcons();
1187
1188        return iconView;
1189    }
1190
1191    private void reloadAllNotificationIcons() {
1192        if (mIconLayout == null) return;
1193        mIconLayout.removeAllViews();
1194        updateNotificationIcons();
1195    }
1196
1197    private void updateNotificationIcons() {
1198        // XXX: need to implement a new limited linear layout class
1199        // to avoid removing & readding everything
1200
1201        if (mIconLayout == null) return;
1202
1203        final LinearLayout.LayoutParams params
1204            = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
1205
1206        int N = mNotns.size();
1207
1208        if (DEBUG) {
1209            Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
1210        }
1211
1212        ArrayList<View> toShow = new ArrayList<View>();
1213
1214        // When IME button is visible, the number of notification icons should be decremented
1215        // to fit the upper limit.
1216        final int maxNotificationIconsCount =
1217                (mInputMethodSwitchButton.getVisibility() != View.GONE) ?
1218                        MAX_NOTIFICATION_ICONS_IME_BUTTON_VISIBLE : MAX_NOTIFICATION_ICONS;
1219        for (int i=0; i< maxNotificationIconsCount; i++) {
1220            if (i>=N) break;
1221            toShow.add(mNotns.get(N-i-1).icon);
1222        }
1223
1224        ArrayList<View> toRemove = new ArrayList<View>();
1225        for (int i=0; i<mIconLayout.getChildCount(); i++) {
1226            View child = mIconLayout.getChildAt(i);
1227            if (!toShow.contains(child)) {
1228                toRemove.add(child);
1229            }
1230        }
1231
1232        for (View remove : toRemove) {
1233            mIconLayout.removeView(remove);
1234        }
1235
1236        for (int i=0; i<toShow.size(); i++) {
1237            View v = toShow.get(i);
1238            v.setPadding(mIconHPadding, 0, mIconHPadding, 0);
1239            if (v.getParent() == null) {
1240                mIconLayout.addView(v, i, params);
1241            }
1242        }
1243
1244        loadNotificationPanel();
1245    }
1246
1247    private void loadNotificationPanel() {
1248        int N = mNotns.size();
1249
1250        ArrayList<View> toShow = new ArrayList<View>();
1251
1252        for (int i=0; i<N; i++) {
1253            View row = mNotns.get(N-i-1).row;
1254            toShow.add(row);
1255        }
1256
1257        ArrayList<View> toRemove = new ArrayList<View>();
1258        for (int i=0; i<mPile.getChildCount(); i++) {
1259            View child = mPile.getChildAt(i);
1260            if (!toShow.contains(child)) {
1261                toRemove.add(child);
1262            }
1263        }
1264
1265        for (View remove : toRemove) {
1266            mPile.removeView(remove);
1267        }
1268
1269        for (int i=0; i<toShow.size(); i++) {
1270            View v = toShow.get(i);
1271            if (v.getParent() == null) {
1272                mPile.addView(toShow.get(i));
1273            }
1274        }
1275
1276        mNotificationPanel.setNotificationCount(N);
1277    }
1278
1279    void workAroundBadLayerDrawableOpacity(View v) {
1280        LayerDrawable d = (LayerDrawable)v.getBackground();
1281        if (d == null) return;
1282        v.setBackgroundDrawable(null);
1283        d.setOpacity(PixelFormat.TRANSLUCENT);
1284        v.setBackgroundDrawable(d);
1285    }
1286
1287    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
1288        StatusBarNotification sbn = entry.notification;
1289        RemoteViews remoteViews = sbn.notification.contentView;
1290        if (remoteViews == null) {
1291            return false;
1292        }
1293
1294        // create the row view
1295        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
1296                Context.LAYOUT_INFLATER_SERVICE);
1297        View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
1298        workAroundBadLayerDrawableOpacity(row);
1299        View vetoButton = row.findViewById(R.id.veto);
1300        if (entry.notification.isClearable()) {
1301            final String _pkg = sbn.pkg;
1302            final String _tag = sbn.tag;
1303            final int _id = sbn.id;
1304            vetoButton.setOnClickListener(new View.OnClickListener() {
1305                    public void onClick(View v) {
1306                        try {
1307                            mBarService.onNotificationClear(_pkg, _tag, _id);
1308                        } catch (RemoteException ex) {
1309                            // system process is dead if we're here.
1310                        }
1311                    }
1312                });
1313        } else {
1314            if ((sbn.notification.flags & Notification.FLAG_ONGOING_EVENT) == 0) {
1315                vetoButton.setVisibility(View.INVISIBLE);
1316            } else {
1317                vetoButton.setVisibility(View.GONE);
1318            }
1319        }
1320
1321        // the large icon
1322        ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
1323        if (sbn.notification.largeIcon != null) {
1324            largeIcon.setImageBitmap(sbn.notification.largeIcon);
1325        } else {
1326            largeIcon.getLayoutParams().width = 0;
1327            largeIcon.setVisibility(View.INVISIBLE);
1328        }
1329
1330        // bind the click event to the content area
1331        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
1332        // XXX: update to allow controls within notification views
1333        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
1334//        content.setOnFocusChangeListener(mFocusChangeListener);
1335        PendingIntent contentIntent = sbn.notification.contentIntent;
1336        if (contentIntent != null) {
1337            content.setOnClickListener(new NotificationClicker(contentIntent,
1338                        sbn.pkg, sbn.tag, sbn.id));
1339        } else {
1340            content.setOnClickListener(null);
1341        }
1342
1343        View expanded = null;
1344        Exception exception = null;
1345        try {
1346            expanded = remoteViews.apply(mContext, content);
1347        }
1348        catch (RuntimeException e) {
1349            exception = e;
1350        }
1351        if (expanded == null) {
1352            final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
1353            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
1354            return false;
1355        } else {
1356            content.addView(expanded);
1357            row.setDrawingCacheEnabled(true);
1358        }
1359
1360        entry.row = row;
1361        entry.content = content;
1362        entry.expanded = expanded;
1363        entry.largeIcon = largeIcon;
1364
1365        return true;
1366    }
1367
1368/*
1369    public class ShadowController {
1370        boolean mShowShadows;
1371        Map<View, View> mShadowsForElements = new IdentityHashMap<View, View>(7);
1372        Map<View, View> mElementsForShadows = new IdentityHashMap<View, View>(7);
1373        LayoutTransition mElementTransition, mShadowTransition;
1374
1375        View mTouchTarget;
1376
1377        ShadowController(boolean showShadows) {
1378            mShowShadows = showShadows;
1379            mTouchTarget = null;
1380
1381            mElementTransition = new LayoutTransition();
1382//            AnimatorSet s = new AnimatorSet();
1383//            s.play(ObjectAnimator.ofInt(null, "top", 48, 0))
1384//                .with(ObjectAnimator.ofFloat(null, "scaleY", 0.5f, 1f))
1385//                .with(ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f))
1386//                ;
1387            mElementTransition.setAnimator(LayoutTransition.APPEARING, //s);
1388                   ObjectAnimator.ofInt(null, "top", 48, 0));
1389            mElementTransition.setDuration(LayoutTransition.APPEARING, 100);
1390            mElementTransition.setStartDelay(LayoutTransition.APPEARING, 0);
1391
1392//            s = new AnimatorSet();
1393//            s.play(ObjectAnimator.ofInt(null, "top", 0, 48))
1394//                .with(ObjectAnimator.ofFloat(null, "scaleY", 1f, 0.5f))
1395//                .with(ObjectAnimator.ofFloat(null, "alpha", 1f, 0.5f))
1396//                ;
1397            mElementTransition.setAnimator(LayoutTransition.DISAPPEARING, //s);
1398                    ObjectAnimator.ofInt(null, "top", 0, 48));
1399            mElementTransition.setDuration(LayoutTransition.DISAPPEARING, 400);
1400
1401            mShadowTransition = new LayoutTransition();
1402            mShadowTransition.setAnimator(LayoutTransition.APPEARING,
1403                    ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
1404            mShadowTransition.setDuration(LayoutTransition.APPEARING, 200);
1405            mShadowTransition.setStartDelay(LayoutTransition.APPEARING, 100);
1406            mShadowTransition.setAnimator(LayoutTransition.DISAPPEARING,
1407                    ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
1408            mShadowTransition.setDuration(LayoutTransition.DISAPPEARING, 100);
1409
1410            ViewGroup bar = (ViewGroup) TabletStatusBar.this.mBarContents;
1411            bar.setLayoutTransition(mElementTransition);
1412            ViewGroup nav = (ViewGroup) TabletStatusBar.this.mNavigationArea;
1413            nav.setLayoutTransition(mElementTransition);
1414            ViewGroup shadowGroup = (ViewGroup) bar.findViewById(R.id.shadows);
1415            shadowGroup.setLayoutTransition(mShadowTransition);
1416        }
1417
1418        public void add(View element, View shadow) {
1419            shadow.setOnTouchListener(makeTouchListener());
1420            mShadowsForElements.put(element, shadow);
1421            mElementsForShadows.put(shadow, element);
1422        }
1423
1424        public boolean getShadowState() {
1425            return mShowShadows;
1426        }
1427
1428        public View.OnTouchListener makeTouchListener() {
1429            return new View.OnTouchListener() {
1430                public boolean onTouch(View v, MotionEvent ev) {
1431                    final int action = ev.getAction();
1432
1433                    if (DEBUG) Slog.d(TAG, "ShadowController: v=" + v + ", ev=" + ev);
1434
1435                    // currently redirecting events?
1436                    if (mTouchTarget == null) {
1437                        mTouchTarget = mElementsForShadows.get(v);
1438                    }
1439
1440                    if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) {
1441                        boolean last = false;
1442                        switch (action) {
1443                            case MotionEvent.ACTION_CANCEL:
1444                            case MotionEvent.ACTION_UP:
1445                                mHandler.removeMessages(MSG_RESTORE_SHADOWS);
1446                                if (mShowShadows) {
1447                                    mHandler.sendEmptyMessageDelayed(MSG_RESTORE_SHADOWS,
1448                                            v == mNotificationShadow ? 5000 : 500);
1449                                }
1450                                last = true;
1451                                break;
1452                            case MotionEvent.ACTION_DOWN:
1453                                mHandler.removeMessages(MSG_RESTORE_SHADOWS);
1454                                setElementShadow(mTouchTarget, false);
1455                                break;
1456                        }
1457                        mTouchTarget.dispatchTouchEvent(ev);
1458                        if (last) mTouchTarget = null;
1459                        return true;
1460                    }
1461
1462                    return false;
1463                }
1464            };
1465        }
1466
1467        public void refresh() {
1468            for (View element : mShadowsForElements.keySet()) {
1469                setElementShadow(element, mShowShadows);
1470            }
1471        }
1472
1473        public void showAllShadows() {
1474            mShowShadows = true;
1475            refresh();
1476        }
1477
1478        public void hideAllShadows() {
1479            mShowShadows = false;
1480            refresh();
1481        }
1482
1483        // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are
1484        // disabled (and should not be shadowed or re-shown)
1485        public void setElementShadow(View button, boolean shade) {
1486            View shadow = mShadowsForElements.get(button);
1487            if (shadow != null) {
1488                if (button.getVisibility() != View.GONE) {
1489                    shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE);
1490                    button.setVisibility(shade ? View.INVISIBLE : View.VISIBLE);
1491                }
1492            }
1493        }
1494
1495        // Hide both element and shadow, using default layout animations.
1496        public void hideElement(View button) {
1497            Slog.d(TAG, "hiding: " + button);
1498            View shadow = mShadowsForElements.get(button);
1499            if (shadow != null) {
1500                shadow.setVisibility(View.GONE);
1501            }
1502            button.setVisibility(View.GONE);
1503        }
1504
1505        // Honoring the current shadow state.
1506        public void showElement(View button) {
1507            Slog.d(TAG, "showing: " + button);
1508            View shadow = mShadowsForElements.get(button);
1509            if (shadow != null) {
1510                shadow.setVisibility(mShowShadows ? View.VISIBLE : View.INVISIBLE);
1511            }
1512            button.setVisibility(mShowShadows ? View.INVISIBLE : View.VISIBLE);
1513        }
1514    }
1515    */
1516
1517    public class TouchOutsideListener implements View.OnTouchListener {
1518        private int mMsg;
1519        private StatusBarPanel mPanel;
1520
1521        public TouchOutsideListener(int msg, StatusBarPanel panel) {
1522            mMsg = msg;
1523            mPanel = panel;
1524        }
1525
1526        public boolean onTouch(View v, MotionEvent ev) {
1527            final int action = ev.getAction();
1528            if (action == MotionEvent.ACTION_OUTSIDE
1529                    || (action == MotionEvent.ACTION_DOWN
1530                        && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
1531                mHandler.removeMessages(mMsg);
1532                mHandler.sendEmptyMessage(mMsg);
1533                return true;
1534            }
1535            return false;
1536        }
1537    }
1538
1539    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1540        pw.print("mDisabled=0x");
1541        pw.println(Integer.toHexString(mDisabled));
1542        pw.println("mNetworkController:");
1543        mNetworkController.dump(fd, pw, args);
1544    }
1545}
1546
1547
1548