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