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