TabletStatusBar.java revision ec51a82bd6e7a33fe6ed84ba252b82625629eaac
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);
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);
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) {
711        // Don't show the ticker when the windowshade is open.
712        if (mNotificationPanel.isShowing()) {
713            return;
714        }
715        // Show the ticker if one is requested. Also don't do this
716        // until status bar window is attached to the window manager,
717        // because...  well, what's the point otherwise?  And trying to
718        // run a ticker without being attached will crash!
719        if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
720            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
721                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
722                mTicker.add(key, n);
723                mNotificationAndImeArea.setVisibility(View.GONE);
724            }
725        }
726    }
727
728    // called by TabletTicker when it's done with all queued ticks
729    public void doneTicking() {
730        mNotificationAndImeArea.setVisibility(View.VISIBLE);
731    }
732
733    public void animateExpand() {
734        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
735        mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
736    }
737
738    public void animateCollapse() {
739        mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
740        mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
741        mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
742        mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
743    }
744
745    // called by StatusBar
746    @Override
747    public void setLightsOn(boolean on) {
748        // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app
749        // that can't handle lights-out mode.
750        if (mMenuButton.getVisibility() == View.VISIBLE) {
751            on = true;
752        }
753        mHandler.removeMessages(MSG_HIDE_CHROME);
754        mHandler.removeMessages(MSG_SHOW_CHROME);
755        mHandler.sendEmptyMessage(on ? MSG_SHOW_CHROME : MSG_HIDE_CHROME);
756    }
757
758    public void setMenuKeyVisible(boolean visible) {
759        if (DEBUG) {
760            Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button");
761        }
762        mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE);
763
764        // See above re: lights-out policy for legacy apps.
765        if (visible) setLightsOn(true);
766    }
767
768    public void setIMEButtonVisible(IBinder token, boolean visible) {
769        if (DEBUG) {
770            Slog.d(TAG, (visible?"showing":"hiding") + " the IME button");
771        }
772        mInputMethodSwitchButton.setIMEButtonVisible(token, visible);
773        mBackButton.setImageResource(
774                visible ? R.drawable.ic_sysbar_back_ime : R.drawable.ic_sysbar_back);
775        if (FAKE_SPACE_BAR) {
776            mFakeSpaceBar.setVisibility(visible ? View.VISIBLE : View.GONE);
777        }
778    }
779
780    private boolean isImmersive() {
781        try {
782            return ActivityManagerNative.getDefault().isTopActivityImmersive();
783            //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
784        } catch (RemoteException ex) {
785            // the end is nigh
786            return false;
787        }
788    }
789
790    private void setAreThereNotifications() {
791        final boolean hasClearable = mNotns.hasClearableItems();
792    }
793
794    /**
795     * Cancel this notification and tell the status bar service about the failure. Hold no locks.
796     */
797    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
798        removeNotification(key);
799        try {
800            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
801        } catch (RemoteException ex) {
802            // The end is nigh.
803        }
804    }
805
806    private void sendKey(KeyEvent key) {
807        try {
808            if (DEBUG) Slog.d(TAG, "injecting key event: " + key);
809            mWindowManager.injectInputEventNoWait(key);
810        } catch (RemoteException ex) {
811        }
812    }
813
814    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
815        public void onClick(View v) {
816            if (v == mNotificationTrigger) {
817                onClickNotificationTrigger();
818            } else if (v == mRecentButton) {
819                onClickRecentButton();
820            }
821        }
822    };
823
824    public void onClickNotificationTrigger() {
825        if (DEBUG) Slog.d(TAG, "clicked notification icons; disabled=" + mDisabled);
826        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
827            if (!mNotificationsOn) {
828                mNotificationsOn = true;
829                mIconLayout.setVisibility(View.VISIBLE); // TODO: animation
830            } else {
831                int msg = !mNotificationPanel.isShowing()
832                    ? MSG_OPEN_NOTIFICATION_PANEL
833                    : MSG_CLOSE_NOTIFICATION_PANEL;
834                mHandler.removeMessages(msg);
835                mHandler.sendEmptyMessage(msg);
836            }
837        }
838    }
839
840    public void onClickRecentButton() {
841        if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled);
842        if (mRecentsPanel == null) {
843            Intent intent = new Intent();
844            intent.setClass(mContext, RecentApplicationsActivity.class);
845            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
846                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
847            mContext.startActivity(intent);
848        } else {
849            if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
850                int msg = (mRecentsPanel.getVisibility() == View.GONE)
851                    ? MSG_OPEN_RECENTS_PANEL
852                    : MSG_CLOSE_RECENTS_PANEL;
853                mHandler.removeMessages(msg);
854                mHandler.sendEmptyMessage(msg);
855            }
856        }
857    }
858
859    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
860        return new NotificationClicker(intent, pkg, tag, id);
861    }
862
863    private class NotificationClicker implements View.OnClickListener {
864        private PendingIntent mIntent;
865        private String mPkg;
866        private String mTag;
867        private int mId;
868
869        NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
870            mIntent = intent;
871            mPkg = pkg;
872            mTag = tag;
873            mId = id;
874        }
875
876        public void onClick(View v) {
877            try {
878                // The intent we are sending is for the application, which
879                // won't have permission to immediately start an activity after
880                // the user switches to home.  We know it is safe to do at this
881                // point, so make sure new activity switches are now allowed.
882                ActivityManagerNative.getDefault().resumeAppSwitches();
883            } catch (RemoteException e) {
884            }
885
886            if (mIntent != null) {
887                int[] pos = new int[2];
888                v.getLocationOnScreen(pos);
889                Intent overlay = new Intent();
890                overlay.setSourceBounds(
891                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
892                try {
893                    mIntent.send(mContext, 0, overlay);
894                } catch (PendingIntent.CanceledException e) {
895                    // the stack trace isn't very helpful here.  Just log the exception message.
896                    Slog.w(TAG, "Sending contentIntent failed: " + e);
897                }
898            }
899
900            try {
901                mBarService.onNotificationClick(mPkg, mTag, mId);
902            } catch (RemoteException ex) {
903                // system process is dead if we're here.
904            }
905
906            // close the shade if it was open
907            animateCollapse();
908
909            // If this click was on the intruder alert, hide that instead
910//            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
911        }
912    }
913
914    StatusBarNotification removeNotificationViews(IBinder key) {
915        NotificationData.Entry entry = mNotns.remove(key);
916        if (entry == null) {
917            Slog.w(TAG, "removeNotification for unknown key: " + key);
918            return null;
919        }
920        // Remove the expanded view.
921        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
922        if (rowParent != null) rowParent.removeView(entry.row);
923
924        if (key == mNotificationPeekKey) {
925            // must close the peek as well, since it's gone
926            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
927        }
928        // Remove the icon.
929//        ViewGroup iconParent = (ViewGroup)entry.icon.getParent();
930//        if (iconParent != null) iconParent.removeView(entry.icon);
931        updateNotificationIcons();
932
933        return entry.notification;
934    }
935
936    private class NotificationIconTouchListener implements View.OnTouchListener {
937        VelocityTracker mVT;
938
939        public NotificationIconTouchListener() {
940        }
941
942        public boolean onTouch(View v, MotionEvent event) {
943            boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
944            boolean panelShowing = mNotificationPanel.isShowing();
945            if (panelShowing) return false;
946
947            switch (event.getAction()) {
948                case MotionEvent.ACTION_DOWN:
949                    mVT = VelocityTracker.obtain();
950
951                    // fall through
952                case MotionEvent.ACTION_OUTSIDE:
953                case MotionEvent.ACTION_MOVE:
954                    // peek and switch icons if necessary
955                    int numIcons = mIconLayout.getChildCount();
956                    int peekIndex = (int)((float)event.getX() * numIcons / mIconLayout.getWidth());
957                    if (peekIndex > numIcons - 1) peekIndex = numIcons - 1;
958                    else if (peekIndex < 0) peekIndex = 0;
959
960                    if (!peeking || mNotificationPeekIndex != peekIndex) {
961                        if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex);
962                        Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
963                        peekMsg.arg1 = peekIndex;
964
965                        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
966
967                        // no delay if we're scrubbing left-right
968                        mHandler.sendMessage(peekMsg);
969                    }
970
971                    // check for fling
972                    if (mVT != null) {
973                        mVT.addMovement(event);
974                        mVT.computeCurrentVelocity(1000);
975                        // require a little more oomph once we're already in peekaboo mode
976                        if (!panelShowing && (
977                               (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
978                            || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
979                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
980                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
981                            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
982                            mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
983                        }
984                    }
985                    return true;
986                case MotionEvent.ACTION_UP:
987                case MotionEvent.ACTION_CANCEL:
988                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
989                    if (peeking) {
990                        mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 5000);
991                    }
992                    mVT.recycle();
993                    mVT = null;
994                    return true;
995            }
996            return false;
997        }
998    }
999
1000    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
1001        if (DEBUG) {
1002            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
1003        }
1004        // Construct the icon.
1005        final StatusBarIconView iconView = new StatusBarIconView(mContext,
1006                notification.pkg + "/0x" + Integer.toHexString(notification.id));
1007        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1008
1009        final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
1010                    notification.notification.icon,
1011                    notification.notification.iconLevel,
1012                    notification.notification.number);
1013        if (!iconView.set(ic)) {
1014            handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
1015            return null;
1016        }
1017        // Construct the expanded view.
1018        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
1019        if (!inflateViews(entry, mPile)) {
1020            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
1021                    + notification);
1022            return null;
1023        }
1024
1025        // Add the icon.
1026        mNotns.add(entry);
1027        updateNotificationIcons();
1028
1029        return iconView;
1030    }
1031
1032    private void reloadAllNotificationIcons() {
1033        if (mIconLayout == null) return;
1034        mIconLayout.removeAllViews();
1035        updateNotificationIcons();
1036    }
1037
1038    private void updateNotificationIcons() {
1039        // XXX: need to implement a new limited linear layout class
1040        // to avoid removing & readding everything
1041
1042        if (mIconLayout == null) return;
1043
1044        final LinearLayout.LayoutParams params
1045            = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mBarHeight);
1046
1047        int N = mNotns.size();
1048
1049        if (DEBUG) {
1050            Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
1051        }
1052
1053        ArrayList<View> toShow = new ArrayList<View>();
1054
1055        for (int i=0; i<MAX_NOTIFICATION_ICONS; i++) {
1056            if (i>=N) break;
1057            toShow.add(mNotns.get(N-i-1).icon);
1058        }
1059
1060        ArrayList<View> toRemove = new ArrayList<View>();
1061        for (int i=0; i<mIconLayout.getChildCount(); i++) {
1062            View child = mIconLayout.getChildAt(i);
1063            if (!toShow.contains(child)) {
1064                toRemove.add(child);
1065            }
1066        }
1067
1068        for (View remove : toRemove) {
1069            mIconLayout.removeView(remove);
1070        }
1071
1072        for (int i=0; i<toShow.size(); i++) {
1073            View v = toShow.get(i);
1074            v.setPadding(mIconHPadding, 0, mIconHPadding, 0);
1075            if (v.getParent() == null) {
1076                mIconLayout.addView(v, i, params);
1077            }
1078        }
1079
1080        loadNotificationPanel();
1081    }
1082
1083    private void loadNotificationPanel() {
1084        int N = mNotns.size();
1085
1086        ArrayList<View> toShow = new ArrayList<View>();
1087
1088        for (int i=0; i<N; i++) {
1089            View row = mNotns.get(N-i-1).row;
1090            toShow.add(row);
1091        }
1092
1093        ArrayList<View> toRemove = new ArrayList<View>();
1094        for (int i=0; i<mPile.getChildCount(); i++) {
1095            View child = mPile.getChildAt(i);
1096            if (!toShow.contains(child)) {
1097                toRemove.add(child);
1098            }
1099        }
1100
1101        for (View remove : toRemove) {
1102            mPile.removeView(remove);
1103        }
1104
1105        for (int i=0; i<toShow.size(); i++) {
1106            View v = toShow.get(i);
1107            if (v.getParent() == null) {
1108                mPile.addView(toShow.get(i));
1109            }
1110        }
1111    }
1112
1113    void workAroundBadLayerDrawableOpacity(View v) {
1114        LayerDrawable d = (LayerDrawable)v.getBackground();
1115        v.setBackgroundDrawable(null);
1116        d.setOpacity(PixelFormat.TRANSLUCENT);
1117        v.setBackgroundDrawable(d);
1118    }
1119
1120    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
1121        StatusBarNotification sbn = entry.notification;
1122        RemoteViews remoteViews = sbn.notification.contentView;
1123        if (remoteViews == null) {
1124            return false;
1125        }
1126
1127        // create the row view
1128        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
1129                Context.LAYOUT_INFLATER_SERVICE);
1130        View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
1131        workAroundBadLayerDrawableOpacity(row);
1132        View vetoButton = row.findViewById(R.id.veto);
1133        if (entry.notification.isClearable()) {
1134            final String _pkg = sbn.pkg;
1135            final String _tag = sbn.tag;
1136            final int _id = sbn.id;
1137            vetoButton.setOnClickListener(new View.OnClickListener() {
1138                    public void onClick(View v) {
1139                        try {
1140                            mBarService.onNotificationClear(_pkg, _tag, _id);
1141                        } catch (RemoteException ex) {
1142                            // system process is dead if we're here.
1143                        }
1144                    }
1145                });
1146        } else {
1147            vetoButton.setVisibility(View.INVISIBLE);
1148        }
1149
1150        // the large icon
1151        ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
1152        if (sbn.notification.largeIcon != null) {
1153            largeIcon.setImageBitmap(sbn.notification.largeIcon);
1154        } else {
1155            largeIcon.getLayoutParams().width = 0;
1156            largeIcon.setVisibility(View.INVISIBLE);
1157        }
1158
1159        // bind the click event to the content area
1160        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
1161        // XXX: update to allow controls within notification views
1162        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
1163//        content.setOnFocusChangeListener(mFocusChangeListener);
1164        PendingIntent contentIntent = sbn.notification.contentIntent;
1165        if (contentIntent != null) {
1166            content.setOnClickListener(new NotificationClicker(contentIntent,
1167                        sbn.pkg, sbn.tag, sbn.id));
1168        } else {
1169            content.setOnClickListener(null);
1170        }
1171
1172        View expanded = null;
1173        Exception exception = null;
1174        try {
1175            expanded = remoteViews.apply(mContext, content);
1176        }
1177        catch (RuntimeException e) {
1178            exception = e;
1179        }
1180        if (expanded == null) {
1181            final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
1182            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
1183            return false;
1184        } else {
1185            content.addView(expanded);
1186            row.setDrawingCacheEnabled(true);
1187        }
1188
1189        entry.row = row;
1190        entry.content = content;
1191        entry.expanded = expanded;
1192
1193        return true;
1194    }
1195
1196/*
1197    public class ShadowController {
1198        boolean mShowShadows;
1199        Map<View, View> mShadowsForElements = new IdentityHashMap<View, View>(7);
1200        Map<View, View> mElementsForShadows = new IdentityHashMap<View, View>(7);
1201        LayoutTransition mElementTransition, mShadowTransition;
1202
1203        View mTouchTarget;
1204
1205        ShadowController(boolean showShadows) {
1206            mShowShadows = showShadows;
1207            mTouchTarget = null;
1208
1209            mElementTransition = new LayoutTransition();
1210//            AnimatorSet s = new AnimatorSet();
1211//            s.play(ObjectAnimator.ofInt(null, "top", 48, 0))
1212//                .with(ObjectAnimator.ofFloat(null, "scaleY", 0.5f, 1f))
1213//                .with(ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f))
1214//                ;
1215            mElementTransition.setAnimator(LayoutTransition.APPEARING, //s);
1216                   ObjectAnimator.ofInt(null, "top", 48, 0));
1217            mElementTransition.setDuration(LayoutTransition.APPEARING, 100);
1218            mElementTransition.setStartDelay(LayoutTransition.APPEARING, 0);
1219
1220//            s = new AnimatorSet();
1221//            s.play(ObjectAnimator.ofInt(null, "top", 0, 48))
1222//                .with(ObjectAnimator.ofFloat(null, "scaleY", 1f, 0.5f))
1223//                .with(ObjectAnimator.ofFloat(null, "alpha", 1f, 0.5f))
1224//                ;
1225            mElementTransition.setAnimator(LayoutTransition.DISAPPEARING, //s);
1226                    ObjectAnimator.ofInt(null, "top", 0, 48));
1227            mElementTransition.setDuration(LayoutTransition.DISAPPEARING, 400);
1228
1229            mShadowTransition = new LayoutTransition();
1230            mShadowTransition.setAnimator(LayoutTransition.APPEARING,
1231                    ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
1232            mShadowTransition.setDuration(LayoutTransition.APPEARING, 200);
1233            mShadowTransition.setStartDelay(LayoutTransition.APPEARING, 100);
1234            mShadowTransition.setAnimator(LayoutTransition.DISAPPEARING,
1235                    ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
1236            mShadowTransition.setDuration(LayoutTransition.DISAPPEARING, 100);
1237
1238            ViewGroup bar = (ViewGroup) TabletStatusBar.this.mBarContents;
1239            bar.setLayoutTransition(mElementTransition);
1240            ViewGroup nav = (ViewGroup) TabletStatusBar.this.mNavigationArea;
1241            nav.setLayoutTransition(mElementTransition);
1242            ViewGroup shadowGroup = (ViewGroup) bar.findViewById(R.id.shadows);
1243            shadowGroup.setLayoutTransition(mShadowTransition);
1244        }
1245
1246        public void add(View element, View shadow) {
1247            shadow.setOnTouchListener(makeTouchListener());
1248            mShadowsForElements.put(element, shadow);
1249            mElementsForShadows.put(shadow, element);
1250        }
1251
1252        public boolean getShadowState() {
1253            return mShowShadows;
1254        }
1255
1256        public View.OnTouchListener makeTouchListener() {
1257            return new View.OnTouchListener() {
1258                public boolean onTouch(View v, MotionEvent ev) {
1259                    final int action = ev.getAction();
1260
1261                    if (DEBUG) Slog.d(TAG, "ShadowController: v=" + v + ", ev=" + ev);
1262
1263                    // currently redirecting events?
1264                    if (mTouchTarget == null) {
1265                        mTouchTarget = mElementsForShadows.get(v);
1266                    }
1267
1268                    if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) {
1269                        boolean last = false;
1270                        switch (action) {
1271                            case MotionEvent.ACTION_CANCEL:
1272                            case MotionEvent.ACTION_UP:
1273                                mHandler.removeMessages(MSG_RESTORE_SHADOWS);
1274                                if (mShowShadows) {
1275                                    mHandler.sendEmptyMessageDelayed(MSG_RESTORE_SHADOWS,
1276                                            v == mNotificationShadow ? 5000 : 500);
1277                                }
1278                                last = true;
1279                                break;
1280                            case MotionEvent.ACTION_DOWN:
1281                                mHandler.removeMessages(MSG_RESTORE_SHADOWS);
1282                                setElementShadow(mTouchTarget, false);
1283                                break;
1284                        }
1285                        mTouchTarget.dispatchTouchEvent(ev);
1286                        if (last) mTouchTarget = null;
1287                        return true;
1288                    }
1289
1290                    return false;
1291                }
1292            };
1293        }
1294
1295        public void refresh() {
1296            for (View element : mShadowsForElements.keySet()) {
1297                setElementShadow(element, mShowShadows);
1298            }
1299        }
1300
1301        public void showAllShadows() {
1302            mShowShadows = true;
1303            refresh();
1304        }
1305
1306        public void hideAllShadows() {
1307            mShowShadows = false;
1308            refresh();
1309        }
1310
1311        // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are
1312        // disabled (and should not be shadowed or re-shown)
1313        public void setElementShadow(View button, boolean shade) {
1314            View shadow = mShadowsForElements.get(button);
1315            if (shadow != null) {
1316                if (button.getVisibility() != View.GONE) {
1317                    shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE);
1318                    button.setVisibility(shade ? View.INVISIBLE : View.VISIBLE);
1319                }
1320            }
1321        }
1322
1323        // Hide both element and shadow, using default layout animations.
1324        public void hideElement(View button) {
1325            Slog.d(TAG, "hiding: " + button);
1326            View shadow = mShadowsForElements.get(button);
1327            if (shadow != null) {
1328                shadow.setVisibility(View.GONE);
1329            }
1330            button.setVisibility(View.GONE);
1331        }
1332
1333        // Honoring the current shadow state.
1334        public void showElement(View button) {
1335            Slog.d(TAG, "showing: " + button);
1336            View shadow = mShadowsForElements.get(button);
1337            if (shadow != null) {
1338                shadow.setVisibility(mShowShadows ? View.VISIBLE : View.INVISIBLE);
1339            }
1340            button.setVisibility(mShowShadows ? View.INVISIBLE : View.VISIBLE);
1341        }
1342    }
1343    */
1344
1345    public class TouchOutsideListener implements View.OnTouchListener {
1346        private int mMsg;
1347        private StatusBarPanel mPanel;
1348
1349        public TouchOutsideListener(int msg, StatusBarPanel panel) {
1350            mMsg = msg;
1351            mPanel = panel;
1352        }
1353
1354        public boolean onTouch(View v, MotionEvent ev) {
1355            final int action = ev.getAction();
1356            if (action == MotionEvent.ACTION_OUTSIDE
1357                    || (action == MotionEvent.ACTION_DOWN
1358                        && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
1359                mHandler.removeMessages(mMsg);
1360                mHandler.sendEmptyMessage(mMsg);
1361                return true;
1362            }
1363            return false;
1364        }
1365    }
1366
1367    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1368        pw.print("mDisabled=0x");
1369        pw.println(Integer.toHexString(mDisabled));
1370    }
1371}
1372
1373
1374