BaseStatusBar.java revision 1927ae8a56a010919a7535231fa0f7db70f7e152
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;
18
19import java.util.ArrayList;
20
21import android.app.ActivityManagerNative;
22import android.app.KeyguardManager;
23import android.app.Notification;
24import android.app.PendingIntent;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.database.ContentObserver;
30import android.graphics.Rect;
31import android.net.Uri;
32import android.os.Build;
33import android.os.Handler;
34import android.os.IBinder;
35import android.os.Message;
36import android.os.RemoteException;
37import android.os.ServiceManager;
38import android.provider.Settings;
39import android.text.TextUtils;
40import android.util.Log;
41import android.util.Slog;
42import android.view.Display;
43import android.view.IWindowManager;
44import android.view.LayoutInflater;
45import android.view.MenuItem;
46import android.view.MotionEvent;
47import android.view.View;
48import android.view.ViewGroup;
49import android.view.ViewGroup.LayoutParams;
50import android.view.WindowManager;
51import android.view.WindowManagerImpl;
52import android.widget.ImageView;
53import android.widget.LinearLayout;
54import android.widget.RemoteViews;
55import android.widget.PopupMenu;
56
57import com.android.internal.statusbar.IStatusBarService;
58import com.android.internal.statusbar.StatusBarIcon;
59import com.android.internal.statusbar.StatusBarIconList;
60import com.android.internal.statusbar.StatusBarNotification;
61import com.android.internal.widget.SizeAdaptiveLayout;
62import com.android.systemui.SearchPanelView;
63import com.android.systemui.SystemUI;
64import com.android.systemui.recent.RecentsPanelView;
65import com.android.systemui.recent.RecentTasksLoader;
66import com.android.systemui.recent.TaskDescription;
67import com.android.systemui.statusbar.CommandQueue;
68import com.android.systemui.statusbar.policy.NotificationRowLayout;
69import com.android.systemui.statusbar.tablet.StatusBarPanel;
70
71import com.android.systemui.R;
72
73public abstract class BaseStatusBar extends SystemUI implements
74    CommandQueue.Callbacks, RecentsPanelView.OnRecentsPanelVisibilityChangedListener {
75    static final String TAG = "StatusBar";
76    private static final boolean DEBUG = false;
77
78    protected static final int MSG_OPEN_RECENTS_PANEL = 1020;
79    protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
80    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
81    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
82    protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
83    protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
84    protected static final int MSG_SHOW_INTRUDER = 1026;
85    protected static final int MSG_HIDE_INTRUDER = 1027;
86
87    protected static final boolean ENABLE_INTRUDERS = false;
88
89    // Should match the value in PhoneWindowManager
90    public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
91
92    public static final int EXPANDED_LEAVE_ALONE = -10000;
93    public static final int EXPANDED_FULL_OPEN = -10001;
94
95    protected CommandQueue mCommandQueue;
96    protected IStatusBarService mBarService;
97    protected H mHandler = createHandler();
98
99    // all notifications
100    protected NotificationData mNotificationData = new NotificationData();
101    protected NotificationRowLayout mPile;
102
103    protected StatusBarNotification mCurrentlyIntrudingNotification;
104
105    // used to notify status bar for suppressing notification LED
106    protected boolean mPanelSlightlyVisible;
107
108    // Search panel
109    protected SearchPanelView mSearchPanelView;
110
111    // Recent apps
112    protected RecentsPanelView mRecentsPanel;
113    protected RecentTasksLoader mRecentTasksLoader;
114
115    protected PopupMenu mNotificationBlamePopup;
116
117    // UI-specific methods
118
119    /**
120     * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
121     * and add them to the window manager.
122     */
123    protected abstract void createAndAddWindows();
124
125    protected Display mDisplay;
126    private IWindowManager mWindowManager;
127    private boolean mDeviceProvisioned = false;
128
129    public IWindowManager getWindowManager() {
130        return mWindowManager;
131    }
132
133    public Display getDisplay() {
134        return mDisplay;
135    }
136
137    public IStatusBarService getStatusBarService() {
138        return mBarService;
139    }
140
141    protected boolean isDeviceProvisioned() {
142        return mDeviceProvisioned;
143    }
144
145    private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
146        @Override
147        public void onChange(boolean selfChange) {
148            final boolean provisioned = 0 != Settings.Secure.getInt(
149                    mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0);
150            if (provisioned != mDeviceProvisioned) {
151                mDeviceProvisioned = provisioned;
152                updateNotificationIcons();
153            }
154        }
155    };
156
157    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
158        @Override
159        public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
160            final boolean isActivity = pendingIntent.isActivity();
161            if (isActivity) {
162                try {
163                    // The intent we are sending is for the application, which
164                    // won't have permission to immediately start an activity after
165                    // the user switches to home.  We know it is safe to do at this
166                    // point, so make sure new activity switches are now allowed.
167                    ActivityManagerNative.getDefault().resumeAppSwitches();
168                    // Also, notifications can be launched from the lock screen,
169                    // so dismiss the lock screen when the activity starts.
170                    ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
171                } catch (RemoteException e) {
172                }
173            }
174
175            boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
176
177            if (isActivity && handled) {
178                // close the shade if it was open
179                animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE);
180                visibilityChanged(false);
181            }
182            return handled;
183        }
184    };
185
186    public void start() {
187        mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
188                .getDefaultDisplay();
189
190        mProvisioningObserver.onChange(false); // set up
191        mContext.getContentResolver().registerContentObserver(
192                Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED), true,
193                mProvisioningObserver);
194
195        mWindowManager = IWindowManager.Stub.asInterface(
196                ServiceManager.getService(Context.WINDOW_SERVICE));
197
198        mBarService = IStatusBarService.Stub.asInterface(
199                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
200
201        // Connect in to the status bar manager service
202        StatusBarIconList iconList = new StatusBarIconList();
203        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
204        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
205        mCommandQueue = new CommandQueue(this, iconList);
206
207        int[] switches = new int[7];
208        ArrayList<IBinder> binders = new ArrayList<IBinder>();
209        try {
210            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
211                    switches, binders);
212        } catch (RemoteException ex) {
213            // If the system process isn't there we're doomed anyway.
214        }
215
216        createAndAddWindows();
217
218        disable(switches[0]);
219        setSystemUiVisibility(switches[1], 0xffffffff);
220        topAppWindowChanged(switches[2] != 0);
221        // StatusBarManagerService has a back up of IME token and it's restored here.
222        setImeWindowStatus(binders.get(0), switches[3], switches[4]);
223        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
224
225        // Set up the initial icon state
226        int N = iconList.size();
227        int viewIndex = 0;
228        for (int i=0; i<N; i++) {
229            StatusBarIcon icon = iconList.getIcon(i);
230            if (icon != null) {
231                addIcon(iconList.getSlot(i), i, viewIndex, icon);
232                viewIndex++;
233            }
234        }
235
236        // Set up the initial notification state
237        N = notificationKeys.size();
238        if (N == notifications.size()) {
239            for (int i=0; i<N; i++) {
240                addNotification(notificationKeys.get(i), notifications.get(i));
241            }
242        } else {
243            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
244                    + " notifications=" + notifications.size());
245        }
246
247        if (DEBUG) {
248            Slog.d(TAG, String.format(
249                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
250                   iconList.size(),
251                   switches[0],
252                   switches[1],
253                   switches[2],
254                   switches[3]
255                   ));
256        }
257    }
258
259    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
260        View vetoButton = row.findViewById(R.id.veto);
261        if (n.isClearable()) {
262            final String _pkg = n.pkg;
263            final String _tag = n.tag;
264            final int _id = n.id;
265            vetoButton.setOnClickListener(new View.OnClickListener() {
266                    public void onClick(View v) {
267                        try {
268                            mBarService.onNotificationClear(_pkg, _tag, _id);
269                        } catch (RemoteException ex) {
270                            // system process is dead if we're here.
271                        }
272                    }
273                });
274            vetoButton.setVisibility(View.VISIBLE);
275        } else {
276            vetoButton.setVisibility(View.GONE);
277        }
278        return vetoButton;
279    }
280
281
282    protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
283        if (sbn.notification.contentView.getLayoutId() !=
284                com.android.internal.R.layout.notification_template_base) {
285            int version = 0;
286            try {
287                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0);
288                version = info.targetSdkVersion;
289            } catch (NameNotFoundException ex) {
290                Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex);
291            }
292            if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
293                content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
294            } else {
295                content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
296            }
297        }
298    }
299
300    private void startApplicationDetailsActivity(String packageName) {
301        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
302                Uri.fromParts("package", packageName, null));
303        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
304        mContext.startActivity(intent);
305    }
306
307    protected View.OnLongClickListener getNotificationLongClicker() {
308        return new View.OnLongClickListener() {
309            @Override
310            public boolean onLongClick(View v) {
311                final String packageNameF = (String) v.getTag();
312                if (packageNameF == null) return false;
313                if (v.getWindowToken() == null) return false;
314                mNotificationBlamePopup = new PopupMenu(mContext, v);
315                mNotificationBlamePopup.getMenuInflater().inflate(
316                        R.menu.notification_popup_menu,
317                        mNotificationBlamePopup.getMenu());
318                mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
319                    public boolean onMenuItemClick(MenuItem item) {
320                        if (item.getItemId() == R.id.notification_inspect_item) {
321                            startApplicationDetailsActivity(packageNameF);
322                            animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE);
323                        } else {
324                            return false;
325                        }
326                        return true;
327                    }
328                });
329                mNotificationBlamePopup.show();
330
331                return true;
332            }
333        };
334    }
335
336    public void dismissPopups() {
337        if (mNotificationBlamePopup != null) {
338            mNotificationBlamePopup.dismiss();
339            mNotificationBlamePopup = null;
340        }
341    }
342
343    public void dismissIntruder() {
344        // pass
345    }
346
347    @Override
348    public void toggleRecentApps() {
349        int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
350            ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
351        mHandler.removeMessages(msg);
352        mHandler.sendEmptyMessage(msg);
353    }
354
355    @Override
356    public void preloadRecentApps() {
357        int msg = MSG_PRELOAD_RECENT_APPS;
358        mHandler.removeMessages(msg);
359        mHandler.sendEmptyMessage(msg);
360    }
361
362    @Override
363    public void cancelPreloadRecentApps() {
364        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
365        mHandler.removeMessages(msg);
366        mHandler.sendEmptyMessage(msg);
367    }
368
369    @Override
370    public void showSearchPanel() {
371        int msg = MSG_OPEN_SEARCH_PANEL;
372        mHandler.removeMessages(msg);
373        mHandler.sendEmptyMessage(msg);
374    }
375
376    @Override
377    public void hideSearchPanel() {
378        int msg = MSG_CLOSE_SEARCH_PANEL;
379        mHandler.removeMessages(msg);
380        mHandler.sendEmptyMessage(msg);
381    }
382
383    @Override
384    public void onRecentsPanelVisibilityChanged(boolean visible) {
385    }
386
387    protected abstract WindowManager.LayoutParams getRecentsLayoutParams(
388            LayoutParams layoutParams);
389
390    protected abstract WindowManager.LayoutParams getSearchLayoutParams(
391            LayoutParams layoutParams);
392
393    protected void updateRecentsPanel(int recentsResId) {
394        // Recents Panel
395        boolean visible = false;
396        ArrayList<TaskDescription> recentTasksList = null;
397        boolean firstScreenful = false;
398        if (mRecentsPanel != null) {
399            visible = mRecentsPanel.isShowing();
400            WindowManagerImpl.getDefault().removeView(mRecentsPanel);
401            if (visible) {
402                recentTasksList = mRecentsPanel.getRecentTasksList();
403                firstScreenful = mRecentsPanel.getFirstScreenful();
404            }
405        }
406
407        // Provide RecentsPanelView with a temporary parent to allow layout params to work.
408        LinearLayout tmpRoot = new LinearLayout(mContext);
409        mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate(
410                recentsResId, tmpRoot, false);
411        mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader);
412        mRecentTasksLoader.setRecentsPanel(mRecentsPanel);
413        mRecentsPanel.setOnTouchListener(
414                 new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, mRecentsPanel));
415        mRecentsPanel.setVisibility(View.GONE);
416
417
418        WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams());
419
420        WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
421        mRecentsPanel.setBar(this);
422        if (visible) {
423            mRecentsPanel.show(true, false, recentTasksList, firstScreenful);
424        }
425
426    }
427
428    protected void updateSearchPanel() {
429        // Search Panel
430        boolean visible = false;
431        if (mSearchPanelView != null) {
432            visible = mSearchPanelView.isShowing();
433            WindowManagerImpl.getDefault().removeView(mSearchPanelView);
434        }
435
436        // Provide SearchPanel with a temporary parent to allow layout params to work.
437        LinearLayout tmpRoot = new LinearLayout(mContext);
438        mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
439                 R.layout.status_bar_search_panel, tmpRoot, false);
440        mSearchPanelView.setOnTouchListener(
441                 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
442        mSearchPanelView.setVisibility(View.GONE);
443
444        WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
445
446        WindowManagerImpl.getDefault().addView(mSearchPanelView, lp);
447        mSearchPanelView.setBar(this);
448        if (visible) {
449            mSearchPanelView.show(true, false);
450        }
451    }
452
453    protected H createHandler() {
454         return new H();
455    }
456
457    static void sendCloseSystemWindows(Context context, String reason) {
458        if (ActivityManagerNative.isSystemReady()) {
459            try {
460                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
461            } catch (RemoteException e) {
462            }
463        }
464    }
465
466    protected class H extends Handler {
467        public void handleMessage(Message m) {
468            switch (m.what) {
469             case MSG_OPEN_RECENTS_PANEL:
470                  if (DEBUG) Slog.d(TAG, "opening recents panel");
471                  if (mRecentsPanel != null) {
472                      mRecentsPanel.show(true, false);
473                  }
474                  break;
475             case MSG_CLOSE_RECENTS_PANEL:
476                  if (DEBUG) Slog.d(TAG, "closing recents panel");
477                  if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
478                      mRecentsPanel.show(false, false);
479                  }
480                  break;
481             case MSG_PRELOAD_RECENT_APPS:
482                  if (DEBUG) Slog.d(TAG, "preloading recents");
483                  mRecentsPanel.preloadRecentTasksList();
484                  break;
485             case MSG_CANCEL_PRELOAD_RECENT_APPS:
486                  if (DEBUG) Slog.d(TAG, "cancel preloading recents");
487                  mRecentsPanel.clearRecentTasksList();
488                  break;
489             case MSG_OPEN_SEARCH_PANEL:
490                 if (DEBUG) Slog.d(TAG, "opening search panel");
491                 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
492                     mSearchPanelView.show(true, true);
493                 }
494                 break;
495             case MSG_CLOSE_SEARCH_PANEL:
496                 if (DEBUG) Slog.d(TAG, "closing search panel");
497                 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
498                     mSearchPanelView.show(false, true);
499                 }
500                 break;
501            }
502        }
503    }
504
505    public class TouchOutsideListener implements View.OnTouchListener {
506        private int mMsg;
507        private StatusBarPanel mPanel;
508
509        public TouchOutsideListener(int msg, StatusBarPanel panel) {
510            mMsg = msg;
511            mPanel = panel;
512        }
513
514        public boolean onTouch(View v, MotionEvent ev) {
515            final int action = ev.getAction();
516            if (action == MotionEvent.ACTION_OUTSIDE
517                || (action == MotionEvent.ACTION_DOWN
518                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
519                mHandler.removeMessages(mMsg);
520                mHandler.sendEmptyMessage(mMsg);
521                return true;
522            }
523            return false;
524        }
525    }
526
527    protected void workAroundBadLayerDrawableOpacity(View v) {
528    }
529
530    protected  boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
531        int rowHeight =
532                mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height);
533        int minHeight =
534                mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
535        int maxHeight =
536                mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
537        StatusBarNotification sbn = entry.notification;
538        RemoteViews oneU = sbn.notification.contentView;
539        RemoteViews large = sbn.notification.bigContentView;
540        if (oneU == null) {
541            return false;
542        }
543
544        // create the row view
545        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
546                Context.LAYOUT_INFLATER_SERVICE);
547        View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
548
549        // for blaming (see SwipeHelper.setLongPressListener)
550        row.setTag(sbn.pkg);
551
552        workAroundBadLayerDrawableOpacity(row);
553        View vetoButton = updateNotificationVetoButton(row, sbn);
554        vetoButton.setContentDescription(mContext.getString(
555                R.string.accessibility_remove_notification));
556
557        // NB: the large icon is now handled entirely by the template
558
559        // bind the click event to the content area
560        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
561        ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
562
563        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
564
565        PendingIntent contentIntent = sbn.notification.contentIntent;
566        if (contentIntent != null) {
567            final View.OnClickListener listener = new NotificationClicker(contentIntent,
568                    sbn.pkg, sbn.tag, sbn.id);
569            content.setOnClickListener(listener);
570        } else {
571            content.setOnClickListener(null);
572        }
573
574        // TODO(cwren) normalize variable names with those in updateNotification
575        View expandedOneU = null;
576        View expandedLarge = null;
577        Exception exception = null;
578        try {
579            oneU.setOnClickHandler(mOnClickHandler);
580            expandedOneU = oneU.apply(mContext, adaptive);
581            if (large != null) {
582                large.setOnClickHandler(mOnClickHandler);
583                expandedLarge = large.apply(mContext, adaptive);
584            }
585        }
586        catch (RuntimeException e) {
587            final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
588            Slog.e(TAG, "couldn't inflate view for notification " + ident, e);
589            return false;
590        }
591
592        if (expandedOneU != null) {
593            SizeAdaptiveLayout.LayoutParams params =
594                    new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams());
595            params.minHeight = minHeight;
596            params.maxHeight = minHeight;
597            adaptive.addView(expandedOneU, params);
598        }
599        if (expandedLarge != null) {
600            SizeAdaptiveLayout.LayoutParams params =
601                    new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams());
602            params.minHeight = minHeight+1;
603            params.maxHeight = maxHeight;
604            adaptive.addView(expandedLarge, params);
605        }
606        row.setDrawingCacheEnabled(true);
607
608        applyLegacyRowBackground(sbn, content);
609
610        row.setTag(R.id.expandable_tag, Boolean.valueOf(large != null));
611        entry.row = row;
612        entry.content = content;
613        entry.expanded = expandedOneU;
614        entry.setLargeView(expandedLarge);
615
616        return true;
617    }
618
619    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
620        return new NotificationClicker(intent, pkg, tag, id);
621    }
622
623    private class NotificationClicker implements View.OnClickListener {
624        private PendingIntent mIntent;
625        private String mPkg;
626        private String mTag;
627        private int mId;
628
629        NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
630            mIntent = intent;
631            mPkg = pkg;
632            mTag = tag;
633            mId = id;
634        }
635
636        public void onClick(View v) {
637            try {
638                // The intent we are sending is for the application, which
639                // won't have permission to immediately start an activity after
640                // the user switches to home.  We know it is safe to do at this
641                // point, so make sure new activity switches are now allowed.
642                ActivityManagerNative.getDefault().resumeAppSwitches();
643                // Also, notifications can be launched from the lock screen,
644                // so dismiss the lock screen when the activity starts.
645                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
646            } catch (RemoteException e) {
647            }
648
649            if (mIntent != null) {
650                int[] pos = new int[2];
651                v.getLocationOnScreen(pos);
652                Intent overlay = new Intent();
653                overlay.setSourceBounds(
654                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
655                try {
656                    mIntent.send(mContext, 0, overlay);
657                } catch (PendingIntent.CanceledException e) {
658                    // the stack trace isn't very helpful here.  Just log the exception message.
659                    Slog.w(TAG, "Sending contentIntent failed: " + e);
660                }
661
662                KeyguardManager kgm =
663                    (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
664                if (kgm != null) kgm.exitKeyguardSecurely(null);
665            }
666
667            try {
668                mBarService.onNotificationClick(mPkg, mTag, mId);
669            } catch (RemoteException ex) {
670                // system process is dead if we're here.
671            }
672
673            // close the shade if it was open
674            animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE);
675            visibilityChanged(false);
676
677            // If this click was on the intruder alert, hide that instead
678//            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
679        }
680    }
681    /**
682     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
683     * This was added last-minute and is inconsistent with the way the rest of the notifications
684     * are handled, because the notification isn't really cancelled.  The lights are just
685     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
686     * this is what he wants. (see bug 1131461)
687     */
688    protected void visibilityChanged(boolean visible) {
689        if (mPanelSlightlyVisible != visible) {
690            mPanelSlightlyVisible = visible;
691            try {
692                mBarService.onPanelRevealed();
693            } catch (RemoteException ex) {
694                // Won't fail unless the world has ended.
695            }
696        }
697    }
698
699    /**
700     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
701     * about the failure.
702     *
703     * WARNING: this will call back into us.  Don't hold any locks.
704     */
705    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
706        removeNotification(key);
707        try {
708            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
709        } catch (RemoteException ex) {
710            // The end is nigh.
711        }
712    }
713
714    protected StatusBarNotification removeNotificationViews(IBinder key) {
715        NotificationData.Entry entry = mNotificationData.remove(key);
716        if (entry == null) {
717            Slog.w(TAG, "removeNotification for unknown key: " + key);
718            return null;
719        }
720        // Remove the expanded view.
721        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
722        if (rowParent != null) rowParent.removeView(entry.row);
723        updateExpansionStates();
724        updateNotificationIcons();
725
726        return entry.notification;
727    }
728
729    protected StatusBarIconView addNotificationViews(IBinder key,
730            StatusBarNotification notification) {
731        if (DEBUG) {
732            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
733        }
734        // Construct the icon.
735        final StatusBarIconView iconView = new StatusBarIconView(mContext,
736                notification.pkg + "/0x" + Integer.toHexString(notification.id),
737                notification.notification);
738        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
739
740        final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
741                    notification.notification.icon,
742                    notification.notification.iconLevel,
743                    notification.notification.number,
744                    notification.notification.tickerText);
745        if (!iconView.set(ic)) {
746            handleNotificationError(key, notification, "Couldn't create icon: " + ic);
747            return null;
748        }
749        // Construct the expanded view.
750        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
751        if (!inflateViews(entry, mPile)) {
752            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
753                    + notification);
754            return null;
755        }
756
757        // Add the expanded view and icon.
758        int pos = mNotificationData.add(entry);
759        if (DEBUG) {
760            Slog.d(TAG, "addNotificationViews: added at " + pos);
761        }
762        updateExpansionStates();
763        updateNotificationIcons();
764
765        return iconView;
766    }
767
768    protected boolean expandView(NotificationData.Entry entry, boolean expand) {
769        int rowHeight =
770                mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height);
771        ViewGroup.LayoutParams lp = entry.row.getLayoutParams();
772        if (entry.expandable() && expand) {
773            if (DEBUG) Slog.d(TAG, "setting expanded row height to WRAP_CONTENT");
774            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
775        } else {
776            if (DEBUG) Slog.d(TAG, "setting collapsed row height to " + rowHeight);
777            lp.height = rowHeight;
778        }
779        entry.row.setLayoutParams(lp);
780        return expand;
781    }
782
783    protected void updateExpansionStates() {
784        int N = mNotificationData.size();
785        for (int i = 0; i < N; i++) {
786            NotificationData.Entry entry = mNotificationData.get(i);
787            if (i == (N-1)) {
788                if (DEBUG) Slog.d(TAG, "expanding top notification at " + i);
789                expandView(entry, true);
790            } else {
791                if (!entry.userExpanded()) {
792                    if (DEBUG) Slog.d(TAG, "collapsing notification at " + i);
793                    expandView(entry, false);
794                } else {
795                    if (DEBUG) Slog.d(TAG, "ignoring user-modified notification at " + i);
796                }
797            }
798        }
799    }
800
801    protected abstract void haltTicker();
802    protected abstract void setAreThereNotifications();
803    protected abstract void updateNotificationIcons();
804    protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
805    protected abstract void updateExpandedViewPos(int expandedPosition);
806    protected abstract int getExpandedViewMaxHeight();
807    protected abstract boolean shouldDisableNavbarGestures();
808
809    protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
810        return parent != null && parent.indexOfChild(entry.row) == 0;
811    }
812
813    public void updateNotification(IBinder key, StatusBarNotification notification) {
814        if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
815
816        final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
817        if (oldEntry == null) {
818            Slog.w(TAG, "updateNotification for unknown key: " + key);
819            return;
820        }
821
822        final StatusBarNotification oldNotification = oldEntry.notification;
823
824        // XXX: modify when we do something more intelligent with the two content views
825        final RemoteViews oldContentView = oldNotification.notification.contentView;
826        final RemoteViews contentView = notification.notification.contentView;
827        final RemoteViews oldBigContentView = oldNotification.notification.bigContentView;
828        final RemoteViews bigContentView = notification.notification.bigContentView;
829
830        if (DEBUG) {
831            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
832                    + " ongoing=" + oldNotification.isOngoing()
833                    + " expanded=" + oldEntry.expanded
834                    + " contentView=" + oldContentView
835                    + " bigContentView=" + oldBigContentView
836                    + " rowParent=" + oldEntry.row.getParent());
837            Slog.d(TAG, "new notification: when=" + notification.notification.when
838                    + " ongoing=" + oldNotification.isOngoing()
839                    + " contentView=" + contentView
840                    + " bigContentView=" + bigContentView);
841        }
842
843        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
844        // didn't change.
845
846        // 1U is never null
847        boolean contentsUnchanged = oldEntry.expanded != null
848                && contentView.getPackage() != null
849                && oldContentView.getPackage() != null
850                && oldContentView.getPackage().equals(contentView.getPackage())
851                && oldContentView.getLayoutId() == contentView.getLayoutId();
852        // large view may be null
853        boolean bigContentsUnchanged =
854                (oldEntry.getLargeView() == null && bigContentView == null)
855                || ((oldEntry.getLargeView() != null && bigContentView != null)
856                    && bigContentView.getPackage() != null
857                    && oldBigContentView.getPackage() != null
858                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
859                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
860        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
861        boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
862                && notification.score == oldNotification.score;
863                // score now encompasses/supersedes isOngoing()
864
865        boolean updateTicker = notification.notification.tickerText != null
866                && !TextUtils.equals(notification.notification.tickerText,
867                        oldEntry.notification.notification.tickerText);
868        boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
869        if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
870            if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
871            oldEntry.notification = notification;
872            try {
873                // Reapply the RemoteViews
874                contentView.reapply(mContext, oldEntry.expanded);
875                if (bigContentView != null && oldEntry.getLargeView() != null) {
876                    bigContentView.reapply(mContext, oldEntry.getLargeView());
877                }
878                // update the contentIntent
879                final PendingIntent contentIntent = notification.notification.contentIntent;
880                if (contentIntent != null) {
881                    final View.OnClickListener listener = makeClicker(contentIntent,
882                            notification.pkg, notification.tag, notification.id);
883                    oldEntry.content.setOnClickListener(listener);
884                } else {
885                    oldEntry.content.setOnClickListener(null);
886                }
887                // Update the icon.
888                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
889                        notification.notification.icon, notification.notification.iconLevel,
890                        notification.notification.number,
891                        notification.notification.tickerText);
892                if (!oldEntry.icon.set(ic)) {
893                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
894                    return;
895                }
896                updateExpansionStates();
897            }
898            catch (RuntimeException e) {
899                // It failed to add cleanly.  Log, and remove the view from the panel.
900                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
901                removeNotificationViews(key);
902                addNotificationViews(key, notification);
903            }
904        } else {
905            if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
906            if (DEBUG) Slog.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
907            if (DEBUG) Slog.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
908            if (DEBUG) Slog.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
909            removeNotificationViews(key);
910            addNotificationViews(key, notification);
911        }
912
913        // Update the veto button accordingly (and as a result, whether this row is
914        // swipe-dismissable)
915        updateNotificationVetoButton(oldEntry.row, notification);
916
917        // Restart the ticker if it's still running
918        if (updateTicker) {
919            haltTicker();
920            tick(key, notification, false);
921        }
922
923        // Recalculate the position of the sliding windows and the titles.
924        setAreThereNotifications();
925        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
926
927        // See if we need to update the intruder.
928        if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) {
929            if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification);
930            // XXX: this is a hack for Alarms. The real implementation will need to *update*
931            // the intruder.
932            if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add()
933                if (DEBUG) Slog.d(TAG, "no longer intrudes!");
934                mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
935            }
936        }
937    }
938}
939