BaseStatusBar.java revision f6e83f452f2223dc99da157f95a11a584f94ea50
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 android.app.ActivityManager;
20import android.app.ActivityManagerNative;
21import android.app.KeyguardManager;
22import android.app.Notification;
23import android.app.PendingIntent;
24import android.app.TaskStackBuilder;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.res.Configuration;
32import android.database.ContentObserver;
33import android.graphics.Rect;
34import android.net.Uri;
35import android.os.Build;
36import android.os.Handler;
37import android.os.IBinder;
38import android.os.Message;
39import android.os.PowerManager;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.os.UserHandle;
43import android.provider.Settings;
44import android.service.dreams.DreamService;
45import android.service.dreams.IDreamManager;
46import android.service.notification.StatusBarNotification;
47import android.text.TextUtils;
48import android.util.Log;
49import android.view.Display;
50import android.view.IWindowManager;
51import android.view.LayoutInflater;
52import android.view.MenuItem;
53import android.view.MotionEvent;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.ViewGroup.LayoutParams;
57import android.view.WindowManager;
58import android.view.WindowManagerGlobal;
59import android.widget.ImageView;
60import android.widget.LinearLayout;
61import android.widget.PopupMenu;
62import android.widget.RemoteViews;
63import android.widget.TextView;
64
65import com.android.internal.statusbar.IStatusBarService;
66import com.android.internal.statusbar.StatusBarIcon;
67import com.android.internal.statusbar.StatusBarIconList;
68import com.android.internal.widget.SizeAdaptiveLayout;
69import com.android.systemui.R;
70import com.android.systemui.RecentsComponent;
71import com.android.systemui.SearchPanelView;
72import com.android.systemui.SystemUI;
73import com.android.systemui.statusbar.policy.NotificationRowLayout;
74
75import java.util.ArrayList;
76import java.util.Locale;
77
78public abstract class BaseStatusBar extends SystemUI implements
79        CommandQueue.Callbacks {
80    public static final String TAG = "StatusBar";
81    public static final boolean DEBUG = false;
82    public static final boolean MULTIUSER_DEBUG = false;
83
84    protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
85    protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
86    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
87    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
88    protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
89    protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
90    protected static final int MSG_SHOW_HEADS_UP = 1026;
91    protected static final int MSG_HIDE_HEADS_UP = 1027;
92    protected static final int MSG_ESCALATE_HEADS_UP = 1028;
93
94    protected static final boolean ENABLE_HEADS_UP = true;
95    // scores above this threshold should be displayed in heads up mode.
96    protected static final int INTERRUPTION_THRESHOLD = 11;
97    protected static final String SETTING_HEADS_UP = "heads_up_enabled";
98
99    // Should match the value in PhoneWindowManager
100    public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
101
102    public static final int EXPANDED_LEAVE_ALONE = -10000;
103    public static final int EXPANDED_FULL_OPEN = -10001;
104
105    protected CommandQueue mCommandQueue;
106    protected IStatusBarService mBarService;
107    protected H mHandler = createHandler();
108
109    // all notifications
110    protected NotificationData mNotificationData = new NotificationData();
111    protected NotificationRowLayout mPile;
112
113    protected NotificationData.Entry mInterruptingNotificationEntry;
114    protected long mInterruptingNotificationTime;
115
116    // used to notify status bar for suppressing notification LED
117    protected boolean mPanelSlightlyVisible;
118
119    // Search panel
120    protected SearchPanelView mSearchPanelView;
121
122    protected PopupMenu mNotificationBlamePopup;
123
124    protected int mCurrentUserId = 0;
125
126    protected int mLayoutDirection;
127    private Locale mLocale;
128    protected boolean mUseHeadsUp = false;
129
130    protected IDreamManager mDreamManager;
131    KeyguardManager mKeyguardManager;
132    PowerManager mPowerManager;
133    protected int mRowHeight;
134
135    // UI-specific methods
136
137    /**
138     * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
139     * and add them to the window manager.
140     */
141    protected abstract void createAndAddWindows();
142
143    protected WindowManager mWindowManager;
144    protected IWindowManager mWindowManagerService;
145    protected abstract void refreshLayout(int layoutDirection);
146
147    protected Display mDisplay;
148
149    private boolean mDeviceProvisioned = false;
150
151    private RecentsComponent mRecents;
152
153    public IStatusBarService getStatusBarService() {
154        return mBarService;
155    }
156
157    public boolean isDeviceProvisioned() {
158        return mDeviceProvisioned;
159    }
160
161    private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
162        @Override
163        public void onChange(boolean selfChange) {
164            final boolean provisioned = 0 != Settings.Global.getInt(
165                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
166            if (provisioned != mDeviceProvisioned) {
167                mDeviceProvisioned = provisioned;
168                updateNotificationIcons();
169            }
170        }
171    };
172
173    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
174        @Override
175        public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
176            if (DEBUG) {
177                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
178            }
179            final boolean isActivity = pendingIntent.isActivity();
180            if (isActivity) {
181                try {
182                    // The intent we are sending is for the application, which
183                    // won't have permission to immediately start an activity after
184                    // the user switches to home.  We know it is safe to do at this
185                    // point, so make sure new activity switches are now allowed.
186                    ActivityManagerNative.getDefault().resumeAppSwitches();
187                    // Also, notifications can be launched from the lock screen,
188                    // so dismiss the lock screen when the activity starts.
189                    ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
190                } catch (RemoteException e) {
191                }
192            }
193
194            boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
195
196            if (isActivity && handled) {
197                // close the shade if it was open
198                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
199                visibilityChanged(false);
200            }
201            return handled;
202        }
203    };
204
205    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
206        @Override
207        public void onReceive(Context context, Intent intent) {
208            String action = intent.getAction();
209            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
210                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
211                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
212                userSwitched(mCurrentUserId);
213            }
214        }
215    };
216
217    public void start() {
218        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
219        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
220        mDisplay = mWindowManager.getDefaultDisplay();
221
222        mDreamManager = IDreamManager.Stub.asInterface(
223                ServiceManager.checkService(DreamService.DREAM_SERVICE));
224        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
225        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
226
227        mProvisioningObserver.onChange(false); // set up
228        mContext.getContentResolver().registerContentObserver(
229                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
230                mProvisioningObserver);
231
232        mBarService = IStatusBarService.Stub.asInterface(
233                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
234
235        mRecents = getComponent(RecentsComponent.class);
236
237        mLocale = mContext.getResources().getConfiguration().locale;
238        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
239
240        // Connect in to the status bar manager service
241        StatusBarIconList iconList = new StatusBarIconList();
242        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
243        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
244        mCommandQueue = new CommandQueue(this, iconList);
245
246        int[] switches = new int[7];
247        ArrayList<IBinder> binders = new ArrayList<IBinder>();
248        try {
249            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
250                    switches, binders);
251        } catch (RemoteException ex) {
252            // If the system process isn't there we're doomed anyway.
253        }
254
255        createAndAddWindows();
256
257        disable(switches[0]);
258        setSystemUiVisibility(switches[1], 0xffffffff);
259        topAppWindowChanged(switches[2] != 0);
260        // StatusBarManagerService has a back up of IME token and it's restored here.
261        setImeWindowStatus(binders.get(0), switches[3], switches[4]);
262        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
263
264        // Set up the initial icon state
265        int N = iconList.size();
266        int viewIndex = 0;
267        for (int i=0; i<N; i++) {
268            StatusBarIcon icon = iconList.getIcon(i);
269            if (icon != null) {
270                addIcon(iconList.getSlot(i), i, viewIndex, icon);
271                viewIndex++;
272            }
273        }
274
275        // Set up the initial notification state
276        N = notificationKeys.size();
277        if (N == notifications.size()) {
278            for (int i=0; i<N; i++) {
279                addNotification(notificationKeys.get(i), notifications.get(i));
280            }
281        } else {
282            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
283                    + " notifications=" + notifications.size());
284        }
285
286        if (DEBUG) {
287            Log.d(TAG, String.format(
288                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
289                   iconList.size(),
290                   switches[0],
291                   switches[1],
292                   switches[2],
293                   switches[3]
294                   ));
295        }
296
297        mCurrentUserId = ActivityManager.getCurrentUser();
298
299        IntentFilter filter = new IntentFilter();
300        filter.addAction(Intent.ACTION_USER_SWITCHED);
301        mContext.registerReceiver(mBroadcastReceiver, filter);
302
303        mLocale = mContext.getResources().getConfiguration().locale;
304    }
305
306    public void userSwitched(int newUserId) {
307        // should be overridden
308    }
309
310    public boolean notificationIsForCurrentUser(StatusBarNotification n) {
311        final int thisUserId = mCurrentUserId;
312        final int notificationUserId = n.getUserId();
313        if (DEBUG && MULTIUSER_DEBUG) {
314            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
315                    n, thisUserId, notificationUserId));
316        }
317        return notificationUserId == UserHandle.USER_ALL
318                || thisUserId == notificationUserId;
319    }
320
321    @Override
322    protected void onConfigurationChanged(Configuration newConfig) {
323        final Locale newLocale = mContext.getResources().getConfiguration().locale;
324        if (! newLocale.equals(mLocale)) {
325            mLocale = newLocale;
326            mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
327            refreshLayout(mLayoutDirection);
328        }
329    }
330
331    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
332        View vetoButton = row.findViewById(R.id.veto);
333        if (n.isClearable() || (mInterruptingNotificationEntry != null
334                && mInterruptingNotificationEntry.row == row)) {
335            final String _pkg = n.getPackageName();
336            final String _tag = n.getTag();
337            final int _id = n.getId();
338            vetoButton.setOnClickListener(new View.OnClickListener() {
339                    public void onClick(View v) {
340                        // Accessibility feedback
341                        v.announceForAccessibility(
342                                mContext.getString(R.string.accessibility_notification_dismissed));
343                        try {
344                            mBarService.onNotificationClear(_pkg, _tag, _id);
345
346                        } catch (RemoteException ex) {
347                            // system process is dead if we're here.
348                        }
349                    }
350                });
351            vetoButton.setVisibility(View.VISIBLE);
352        } else {
353            vetoButton.setVisibility(View.GONE);
354        }
355        vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
356        return vetoButton;
357    }
358
359
360    protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
361        if (sbn.getNotification().contentView.getLayoutId() !=
362                com.android.internal.R.layout.notification_template_base) {
363            int version = 0;
364            try {
365                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
366                version = info.targetSdkVersion;
367            } catch (NameNotFoundException ex) {
368                Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
369            }
370            if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
371                content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
372            } else {
373                content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
374            }
375        }
376    }
377
378    private void startApplicationDetailsActivity(String packageName) {
379        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
380                Uri.fromParts("package", packageName, null));
381        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
382        TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
383                null, UserHandle.CURRENT);
384    }
385
386    protected View.OnLongClickListener getNotificationLongClicker() {
387        return new View.OnLongClickListener() {
388            @Override
389            public boolean onLongClick(View v) {
390                final String packageNameF = (String) v.getTag();
391                if (packageNameF == null) return false;
392                if (v.getWindowToken() == null) return false;
393                mNotificationBlamePopup = new PopupMenu(mContext, v);
394                mNotificationBlamePopup.getMenuInflater().inflate(
395                        R.menu.notification_popup_menu,
396                        mNotificationBlamePopup.getMenu());
397                mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
398                    public boolean onMenuItemClick(MenuItem item) {
399                        if (item.getItemId() == R.id.notification_inspect_item) {
400                            startApplicationDetailsActivity(packageNameF);
401                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
402                        } else {
403                            return false;
404                        }
405                        return true;
406                    }
407                });
408                mNotificationBlamePopup.show();
409
410                return true;
411            }
412        };
413    }
414
415    public void dismissPopups() {
416        if (mNotificationBlamePopup != null) {
417            mNotificationBlamePopup.dismiss();
418            mNotificationBlamePopup = null;
419        }
420    }
421
422    public void onHeadsUpDismissed() {
423    }
424
425    @Override
426    public void toggleRecentApps() {
427        int msg = MSG_TOGGLE_RECENTS_PANEL;
428        mHandler.removeMessages(msg);
429        mHandler.sendEmptyMessage(msg);
430    }
431
432    @Override
433    public void preloadRecentApps() {
434        int msg = MSG_PRELOAD_RECENT_APPS;
435        mHandler.removeMessages(msg);
436        mHandler.sendEmptyMessage(msg);
437    }
438
439    @Override
440    public void cancelPreloadRecentApps() {
441        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
442        mHandler.removeMessages(msg);
443        mHandler.sendEmptyMessage(msg);
444    }
445
446    @Override
447    public void showSearchPanel() {
448        int msg = MSG_OPEN_SEARCH_PANEL;
449        mHandler.removeMessages(msg);
450        mHandler.sendEmptyMessage(msg);
451    }
452
453    @Override
454    public void hideSearchPanel() {
455        int msg = MSG_CLOSE_SEARCH_PANEL;
456        mHandler.removeMessages(msg);
457        mHandler.sendEmptyMessage(msg);
458    }
459
460    protected abstract WindowManager.LayoutParams getSearchLayoutParams(
461            LayoutParams layoutParams);
462
463    protected void updateSearchPanel() {
464        // Search Panel
465        boolean visible = false;
466        if (mSearchPanelView != null) {
467            visible = mSearchPanelView.isShowing();
468            mWindowManager.removeView(mSearchPanelView);
469        }
470
471        // Provide SearchPanel with a temporary parent to allow layout params to work.
472        LinearLayout tmpRoot = new LinearLayout(mContext);
473        mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
474                 R.layout.status_bar_search_panel, tmpRoot, false);
475        mSearchPanelView.setOnTouchListener(
476                 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
477        mSearchPanelView.setVisibility(View.GONE);
478
479        WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
480
481        mWindowManager.addView(mSearchPanelView, lp);
482        mSearchPanelView.setBar(this);
483        if (visible) {
484            mSearchPanelView.show(true, false);
485        }
486    }
487
488    protected H createHandler() {
489         return new H();
490    }
491
492    static void sendCloseSystemWindows(Context context, String reason) {
493        if (ActivityManagerNative.isSystemReady()) {
494            try {
495                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
496            } catch (RemoteException e) {
497            }
498        }
499    }
500
501    protected abstract View getStatusBarView();
502
503    protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
504        // additional optimization when we have software system buttons - start loading the recent
505        // tasks on touch down
506        @Override
507        public boolean onTouch(View v, MotionEvent event) {
508            int action = event.getAction() & MotionEvent.ACTION_MASK;
509            if (action == MotionEvent.ACTION_DOWN) {
510                preloadRecentTasksList();
511            } else if (action == MotionEvent.ACTION_CANCEL) {
512                cancelPreloadingRecentTasksList();
513            } else if (action == MotionEvent.ACTION_UP) {
514                if (!v.isPressed()) {
515                    cancelPreloadingRecentTasksList();
516                }
517
518            }
519            return false;
520        }
521    };
522
523    protected void toggleRecentsActivity() {
524        if (mRecents != null) {
525            mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
526        }
527    }
528
529    protected void preloadRecentTasksList() {
530        if (mRecents != null) {
531            mRecents.preloadRecentTasksList();
532        }
533    }
534
535    protected void cancelPreloadingRecentTasksList() {
536        if (mRecents != null) {
537            mRecents.cancelPreloadingRecentTasksList();
538        }
539    }
540
541    protected void closeRecents() {
542        if (mRecents != null) {
543            mRecents.closeRecents();
544        }
545    }
546
547    public abstract void resetHeadsUpDecayTimer();
548
549    protected class H extends Handler {
550        public void handleMessage(Message m) {
551            Intent intent;
552            switch (m.what) {
553             case MSG_TOGGLE_RECENTS_PANEL:
554                 toggleRecentsActivity();
555                 break;
556             case MSG_CLOSE_RECENTS_PANEL:
557                 closeRecents();
558                 break;
559             case MSG_PRELOAD_RECENT_APPS:
560                  preloadRecentTasksList();
561                  break;
562             case MSG_CANCEL_PRELOAD_RECENT_APPS:
563                  cancelPreloadingRecentTasksList();
564                  break;
565             case MSG_OPEN_SEARCH_PANEL:
566                 if (DEBUG) Log.d(TAG, "opening search panel");
567                 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
568                     mSearchPanelView.show(true, true);
569                 }
570                 break;
571             case MSG_CLOSE_SEARCH_PANEL:
572                 if (DEBUG) Log.d(TAG, "closing search panel");
573                 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
574                     mSearchPanelView.show(false, true);
575                 }
576                 break;
577            }
578        }
579    }
580
581    public class TouchOutsideListener implements View.OnTouchListener {
582        private int mMsg;
583        private StatusBarPanel mPanel;
584
585        public TouchOutsideListener(int msg, StatusBarPanel panel) {
586            mMsg = msg;
587            mPanel = panel;
588        }
589
590        public boolean onTouch(View v, MotionEvent ev) {
591            final int action = ev.getAction();
592            if (action == MotionEvent.ACTION_OUTSIDE
593                || (action == MotionEvent.ACTION_DOWN
594                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
595                mHandler.removeMessages(mMsg);
596                mHandler.sendEmptyMessage(mMsg);
597                return true;
598            }
599            return false;
600        }
601    }
602
603    protected void workAroundBadLayerDrawableOpacity(View v) {
604    }
605
606    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
607        int minHeight =
608                mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
609        int maxHeight =
610                mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
611        StatusBarNotification sbn = entry.notification;
612        RemoteViews contentView = sbn.getNotification().contentView;
613        RemoteViews bigContentView = sbn.getNotification().bigContentView;
614        if (contentView == null) {
615            return false;
616        }
617
618        // create the row view
619        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
620                Context.LAYOUT_INFLATER_SERVICE);
621        ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
622                R.layout.status_bar_notification_row, parent, false);
623
624        // for blaming (see SwipeHelper.setLongPressListener)
625        row.setTag(sbn.getPackageName());
626
627        workAroundBadLayerDrawableOpacity(row);
628        View vetoButton = updateNotificationVetoButton(row, sbn);
629        vetoButton.setContentDescription(mContext.getString(
630                R.string.accessibility_remove_notification));
631
632        // NB: the large icon is now handled entirely by the template
633
634        // bind the click event to the content area
635        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
636        ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
637
638        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
639
640        PendingIntent contentIntent = sbn.getNotification().contentIntent;
641        if (contentIntent != null) {
642            final View.OnClickListener listener = new NotificationClicker(contentIntent,
643                    sbn.getPackageName(), sbn.getTag(), sbn.getId());
644            content.setOnClickListener(listener);
645        } else {
646            content.setOnClickListener(null);
647        }
648
649        View contentViewLocal = null;
650        View bigContentViewLocal = null;
651        try {
652            contentViewLocal = contentView.apply(mContext, adaptive, mOnClickHandler);
653            if (bigContentView != null) {
654                bigContentViewLocal = bigContentView.apply(mContext, adaptive, mOnClickHandler);
655            }
656        }
657        catch (RuntimeException e) {
658            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
659            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
660            return false;
661        }
662
663        if (contentViewLocal != null) {
664            SizeAdaptiveLayout.LayoutParams params =
665                    new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
666            params.minHeight = minHeight;
667            params.maxHeight = minHeight;
668            adaptive.addView(contentViewLocal, params);
669        }
670        if (bigContentViewLocal != null) {
671            SizeAdaptiveLayout.LayoutParams params =
672                    new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
673            params.minHeight = minHeight+1;
674            params.maxHeight = maxHeight;
675            adaptive.addView(bigContentViewLocal, params);
676        }
677        row.setDrawingCacheEnabled(true);
678
679        applyLegacyRowBackground(sbn, content);
680
681        if (MULTIUSER_DEBUG) {
682            TextView debug = (TextView) row.findViewById(R.id.debug_info);
683            if (debug != null) {
684                debug.setVisibility(View.VISIBLE);
685                debug.setText("U " + entry.notification.getUserId());
686            }
687        }
688        entry.row = row;
689        entry.row.setRowHeight(mRowHeight);
690        entry.content = content;
691        entry.expanded = contentViewLocal;
692        entry.setBigContentView(bigContentViewLocal);
693
694        return true;
695    }
696
697    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
698        return new NotificationClicker(intent, pkg, tag, id);
699    }
700
701    protected class NotificationClicker implements View.OnClickListener {
702        private PendingIntent mIntent;
703        private String mPkg;
704        private String mTag;
705        private int mId;
706
707        public NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
708            mIntent = intent;
709            mPkg = pkg;
710            mTag = tag;
711            mId = id;
712        }
713
714        public void onClick(View v) {
715            try {
716                // The intent we are sending is for the application, which
717                // won't have permission to immediately start an activity after
718                // the user switches to home.  We know it is safe to do at this
719                // point, so make sure new activity switches are now allowed.
720                ActivityManagerNative.getDefault().resumeAppSwitches();
721                // Also, notifications can be launched from the lock screen,
722                // so dismiss the lock screen when the activity starts.
723                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
724            } catch (RemoteException e) {
725            }
726
727            if (mIntent != null) {
728                int[] pos = new int[2];
729                v.getLocationOnScreen(pos);
730                Intent overlay = new Intent();
731                overlay.setSourceBounds(
732                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
733                try {
734                    mIntent.send(mContext, 0, overlay);
735                } catch (PendingIntent.CanceledException e) {
736                    // the stack trace isn't very helpful here.  Just log the exception message.
737                    Log.w(TAG, "Sending contentIntent failed: " + e);
738                }
739
740                KeyguardManager kgm =
741                    (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
742                if (kgm != null) kgm.exitKeyguardSecurely(null);
743            }
744
745            try {
746                mBarService.onNotificationClick(mPkg, mTag, mId);
747            } catch (RemoteException ex) {
748                // system process is dead if we're here.
749            }
750
751            // close the shade if it was open
752            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
753            visibilityChanged(false);
754        }
755    }
756    /**
757     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
758     * This was added last-minute and is inconsistent with the way the rest of the notifications
759     * are handled, because the notification isn't really cancelled.  The lights are just
760     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
761     * this is what he wants. (see bug 1131461)
762     */
763    protected void visibilityChanged(boolean visible) {
764        if (mPanelSlightlyVisible != visible) {
765            mPanelSlightlyVisible = visible;
766            try {
767                mBarService.onPanelRevealed();
768            } catch (RemoteException ex) {
769                // Won't fail unless the world has ended.
770            }
771        }
772    }
773
774    /**
775     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
776     * about the failure.
777     *
778     * WARNING: this will call back into us.  Don't hold any locks.
779     */
780    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
781        removeNotification(key);
782        try {
783            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message);
784        } catch (RemoteException ex) {
785            // The end is nigh.
786        }
787    }
788
789    protected StatusBarNotification removeNotificationViews(IBinder key) {
790        NotificationData.Entry entry = mNotificationData.remove(key);
791        if (entry == null) {
792            Log.w(TAG, "removeNotification for unknown key: " + key);
793            return null;
794        }
795        // Remove the expanded view.
796        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
797        if (rowParent != null) rowParent.removeView(entry.row);
798        updateExpansionStates();
799        updateNotificationIcons();
800
801        return entry.notification;
802    }
803
804    protected NotificationData.Entry createNotificationViews(IBinder key,
805            StatusBarNotification notification) {
806        if (DEBUG) {
807            Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification);
808        }
809        // Construct the icon.
810        final StatusBarIconView iconView = new StatusBarIconView(mContext,
811                notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()),
812                notification.getNotification());
813        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
814
815        final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
816                notification.getUser(),
817                    notification.getNotification().icon,
818                    notification.getNotification().iconLevel,
819                    notification.getNotification().number,
820                    notification.getNotification().tickerText);
821        if (!iconView.set(ic)) {
822            handleNotificationError(key, notification, "Couldn't create icon: " + ic);
823            return null;
824        }
825        // Construct the expanded view.
826        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
827        if (!inflateViews(entry, mPile)) {
828            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
829                    + notification);
830            return null;
831        }
832        return entry;
833    }
834
835    protected void addNotificationViews(NotificationData.Entry entry) {
836        // Add the expanded view and icon.
837        int pos = mNotificationData.add(entry);
838        if (DEBUG) {
839            Log.d(TAG, "addNotificationViews: added at " + pos);
840        }
841        updateExpansionStates();
842        updateNotificationIcons();
843    }
844
845    private void addNotificationViews(IBinder key, StatusBarNotification notification) {
846        addNotificationViews(createNotificationViews(key, notification));
847    }
848
849    protected void updateExpansionStates() {
850        int N = mNotificationData.size();
851        for (int i = 0; i < N; i++) {
852            NotificationData.Entry entry = mNotificationData.get(i);
853            if (!entry.row.isUserLocked()) {
854                if (i == (N-1)) {
855                    if (DEBUG) Log.d(TAG, "expanding top notification at " + i);
856                    entry.row.setExpanded(true);
857                } else {
858                    if (!entry.row.isUserExpanded()) {
859                        if (DEBUG) Log.d(TAG, "collapsing notification at " + i);
860                        entry.row.setExpanded(false);
861                    } else {
862                        if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i);
863                    }
864                }
865            } else {
866                if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i);
867            }
868        }
869    }
870
871    protected abstract void haltTicker();
872    protected abstract void setAreThereNotifications();
873    protected abstract void updateNotificationIcons();
874    protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
875    protected abstract void updateExpandedViewPos(int expandedPosition);
876    protected abstract int getExpandedViewMaxHeight();
877    protected abstract boolean shouldDisableNavbarGestures();
878
879    protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
880        return parent != null && parent.indexOfChild(entry.row) == 0;
881    }
882
883    public void updateNotification(IBinder key, StatusBarNotification notification) {
884        if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
885
886        final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
887        if (oldEntry == null) {
888            Log.w(TAG, "updateNotification for unknown key: " + key);
889            return;
890        }
891
892        final StatusBarNotification oldNotification = oldEntry.notification;
893
894        // XXX: modify when we do something more intelligent with the two content views
895        final RemoteViews oldContentView = oldNotification.getNotification().contentView;
896        final RemoteViews contentView = notification.getNotification().contentView;
897        final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
898        final RemoteViews bigContentView = notification.getNotification().bigContentView;
899
900        if (DEBUG) {
901            Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
902                    + " ongoing=" + oldNotification.isOngoing()
903                    + " expanded=" + oldEntry.expanded
904                    + " contentView=" + oldContentView
905                    + " bigContentView=" + oldBigContentView
906                    + " rowParent=" + oldEntry.row.getParent());
907            Log.d(TAG, "new notification: when=" + notification.getNotification().when
908                    + " ongoing=" + oldNotification.isOngoing()
909                    + " contentView=" + contentView
910                    + " bigContentView=" + bigContentView);
911        }
912
913        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
914        // didn't change.
915
916        // 1U is never null
917        boolean contentsUnchanged = oldEntry.expanded != null
918                && contentView.getPackage() != null
919                && oldContentView.getPackage() != null
920                && oldContentView.getPackage().equals(contentView.getPackage())
921                && oldContentView.getLayoutId() == contentView.getLayoutId();
922        // large view may be null
923        boolean bigContentsUnchanged =
924                (oldEntry.getBigContentView() == null && bigContentView == null)
925                || ((oldEntry.getBigContentView() != null && bigContentView != null)
926                    && bigContentView.getPackage() != null
927                    && oldBigContentView.getPackage() != null
928                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
929                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
930        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
931        boolean orderUnchanged = notification.getNotification().when== oldNotification.getNotification().when
932                && notification.getScore() == oldNotification.getScore();
933                // score now encompasses/supersedes isOngoing()
934
935        boolean updateTicker = notification.getNotification().tickerText != null
936                && !TextUtils.equals(notification.getNotification().tickerText,
937                        oldEntry.notification.getNotification().tickerText);
938        boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
939        if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
940            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
941            oldEntry.notification = notification;
942            try {
943                updateNotificationViews(oldEntry, notification);
944
945                if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
946                        && oldNotification == mInterruptingNotificationEntry.notification) {
947                    if (!shouldInterrupt(notification)) {
948                        if (DEBUG) Log.d(TAG, "no longer interrupts!");
949                        mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
950                    } else {
951                        if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
952                        mInterruptingNotificationEntry.notification = notification;
953                        updateNotificationViews(mInterruptingNotificationEntry, notification);
954                    }
955                }
956
957                // Update the icon.
958                final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
959                        notification.getUser(),
960                        notification.getNotification().icon, notification.getNotification().iconLevel,
961                        notification.getNotification().number,
962                        notification.getNotification().tickerText);
963                if (!oldEntry.icon.set(ic)) {
964                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
965                    return;
966                }
967                updateExpansionStates();
968            }
969            catch (RuntimeException e) {
970                // It failed to add cleanly.  Log, and remove the view from the panel.
971                Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
972                removeNotificationViews(key);
973                addNotificationViews(key, notification);
974            }
975        } else {
976            if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
977            if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
978            if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
979            if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
980            final boolean wasExpanded = oldEntry.row.isUserExpanded();
981            removeNotificationViews(key);
982            addNotificationViews(key, notification);  // will also replace the heads up
983            if (wasExpanded) {
984                final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
985                newEntry.row.setExpanded(true);
986                newEntry.row.setUserExpanded(true);
987            }
988        }
989
990        // Update the veto button accordingly (and as a result, whether this row is
991        // swipe-dismissable)
992        updateNotificationVetoButton(oldEntry.row, notification);
993
994        // Is this for you?
995        boolean isForCurrentUser = notificationIsForCurrentUser(notification);
996        if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
997
998        // Restart the ticker if it's still running
999        if (updateTicker && isForCurrentUser) {
1000            haltTicker();
1001            tick(key, notification, false);
1002        }
1003
1004        // Recalculate the position of the sliding windows and the titles.
1005        setAreThereNotifications();
1006        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1007    }
1008
1009    private void updateNotificationViews(NotificationData.Entry entry,
1010            StatusBarNotification notification) {
1011        final RemoteViews contentView = notification.getNotification().contentView;
1012        final RemoteViews bigContentView = notification.getNotification().bigContentView;
1013        // Reapply the RemoteViews
1014        contentView.reapply(mContext, entry.expanded, mOnClickHandler);
1015        if (bigContentView != null && entry.getBigContentView() != null) {
1016            bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler);
1017        }
1018        // update the contentIntent
1019        final PendingIntent contentIntent = notification.getNotification().contentIntent;
1020        if (contentIntent != null) {
1021            final View.OnClickListener listener = makeClicker(contentIntent,
1022                    notification.getPackageName(), notification.getTag(), notification.getId());
1023            entry.content.setOnClickListener(listener);
1024        } else {
1025            entry.content.setOnClickListener(null);
1026        }
1027    }
1028
1029    protected void notifyHeadsUpScreenOn(boolean screenOn) {
1030        if (!screenOn && mInterruptingNotificationEntry != null) {
1031            mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
1032        }
1033    }
1034
1035    protected boolean shouldInterrupt(StatusBarNotification sbn) {
1036        Notification notification = sbn.getNotification();
1037        // some predicates to make the boolean logic legible
1038        boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
1039                || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
1040                || notification.sound != null
1041                || notification.vibrate != null;
1042        boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
1043        boolean isFullscreen = notification.fullScreenIntent != null;
1044        boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
1045                Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
1046
1047        boolean interrupt = (isFullscreen || (isHighPriority && isNoisy))
1048                && isAllowed
1049                && mPowerManager.isScreenOn()
1050                && !mKeyguardManager.isKeyguardLocked();
1051        try {
1052            interrupt = interrupt && !mDreamManager.isDreaming();
1053        } catch (RemoteException e) {
1054            Log.d(TAG, "failed to query dream manager", e);
1055        }
1056        if (DEBUG) Log.d(TAG, "interrupt: " + interrupt);
1057        return interrupt;
1058    }
1059
1060    // Q: What kinds of notifications should show during setup?
1061    // A: Almost none! Only things coming from the system (package is "android") that also
1062    // have special "kind" tags marking them as relevant for setup (see below).
1063    protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
1064        if ("android".equals(sbn.getPackageName())) {
1065            if (sbn.getNotification().kind != null) {
1066                for (String aKind : sbn.getNotification().kind) {
1067                    // IME switcher, created by InputMethodManagerService
1068                    if ("android.system.imeswitcher".equals(aKind)) return true;
1069                    // OTA availability & errors, created by SystemUpdateService
1070                    if ("android.system.update".equals(aKind)) return true;
1071                }
1072            }
1073        }
1074        return false;
1075    }
1076
1077    public boolean inKeyguardRestrictedInputMode() {
1078        KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
1079        return km.inKeyguardRestrictedInputMode();
1080    }
1081
1082    public void setInteracting(int barWindow, boolean interacting) {
1083        // hook for subclasses
1084    }
1085
1086    public void destroy() {
1087        if (mSearchPanelView != null) {
1088            mWindowManager.removeViewImmediate(mSearchPanelView);
1089        }
1090        mContext.unregisterReceiver(mBroadcastReceiver);
1091    }
1092}
1093