BaseStatusBar.java revision 67b2260093774f5866f781aede52830440f4ed0e
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.Notification;
22import android.app.PendingIntent;
23import android.app.TaskStackBuilder;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.pm.UserInfo;
32import android.content.res.ColorStateList;
33import android.content.res.Configuration;
34import android.database.ContentObserver;
35import android.graphics.Color;
36import android.graphics.PorterDuff;
37import android.graphics.Rect;
38import android.graphics.drawable.AnimationDrawable;
39import android.graphics.drawable.BitmapDrawable;
40import android.graphics.drawable.Drawable;
41import android.net.Uri;
42import android.os.Build;
43import android.os.Handler;
44import android.os.IBinder;
45import android.os.Message;
46import android.os.PowerManager;
47import android.os.RemoteException;
48import android.os.ServiceManager;
49import android.os.SystemProperties;
50import android.os.UserHandle;
51import android.os.UserManager;
52import android.provider.Settings;
53import android.service.dreams.DreamService;
54import android.service.dreams.IDreamManager;
55import android.service.notification.StatusBarNotification;
56import android.text.SpannableStringBuilder;
57import android.text.Spanned;
58import android.text.TextUtils;
59import android.text.style.TextAppearanceSpan;
60import android.util.Log;
61import android.util.SparseArray;
62import android.util.SparseBooleanArray;
63import android.view.ContextThemeWrapper;
64import android.view.Display;
65import android.view.IWindowManager;
66import android.view.LayoutInflater;
67import android.view.MenuItem;
68import android.view.MotionEvent;
69import android.view.View;
70import android.view.ViewGroup;
71import android.view.ViewGroup.LayoutParams;
72import android.view.WindowManager;
73import android.view.WindowManagerGlobal;
74import android.widget.Button;
75import android.widget.ImageView;
76import android.widget.LinearLayout;
77import android.widget.PopupMenu;
78import android.widget.RemoteViews;
79import android.widget.TextView;
80
81import com.android.internal.statusbar.IStatusBarService;
82import com.android.internal.statusbar.StatusBarIcon;
83import com.android.internal.statusbar.StatusBarIconList;
84import com.android.internal.widget.SizeAdaptiveLayout;
85import com.android.systemui.ImageUtils;
86import com.android.systemui.R;
87import com.android.systemui.RecentsComponent;
88import com.android.systemui.SearchPanelView;
89import com.android.systemui.SystemUI;
90import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
91
92import java.util.ArrayList;
93import java.util.Locale;
94import java.util.Stack;
95
96public abstract class BaseStatusBar extends SystemUI implements
97        CommandQueue.Callbacks {
98    public static final String TAG = "StatusBar";
99    public static final boolean DEBUG = false;
100    public static final boolean MULTIUSER_DEBUG = false;
101
102    protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
103    protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
104    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
105    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
106    protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
107    protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
108    protected static final int MSG_SHOW_HEADS_UP = 1026;
109    protected static final int MSG_HIDE_HEADS_UP = 1027;
110    protected static final int MSG_ESCALATE_HEADS_UP = 1028;
111
112    public static final boolean ENABLE_NOTIFICATION_STACK = SystemProperties
113            .getBoolean("persist.notifications.use_stack", false);
114    protected static final boolean ENABLE_HEADS_UP = true;
115    // scores above this threshold should be displayed in heads up mode.
116    protected static final int INTERRUPTION_THRESHOLD = 10;
117    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
118
119    // Should match the value in PhoneWindowManager
120    public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
121
122    public static final int EXPANDED_LEAVE_ALONE = -10000;
123    public static final int EXPANDED_FULL_OPEN = -10001;
124
125    private static final String EXTRA_INTERCEPT = "android.intercept";
126    private static final float INTERCEPTED_ALPHA = .2f;
127
128    protected CommandQueue mCommandQueue;
129    protected IStatusBarService mBarService;
130    protected H mHandler = createHandler();
131
132    // all notifications
133    protected NotificationData mNotificationData = new NotificationData();
134    protected ViewGroup mPile;
135
136    protected NotificationData.Entry mInterruptingNotificationEntry;
137    protected long mInterruptingNotificationTime;
138
139    // used to notify status bar for suppressing notification LED
140    protected boolean mPanelSlightlyVisible;
141
142    // Search panel
143    protected SearchPanelView mSearchPanelView;
144
145    protected PopupMenu mNotificationBlamePopup;
146
147    protected int mCurrentUserId = 0;
148    final protected SparseArray<UserInfo> mRelatedUsers = new SparseArray<UserInfo>();
149
150    protected int mLayoutDirection = -1; // invalid
151    private Locale mLocale;
152    protected boolean mUseHeadsUp = false;
153    protected boolean mHeadsUpTicker = false;
154
155    protected IDreamManager mDreamManager;
156    PowerManager mPowerManager;
157    protected int mRowHeight;
158
159    // public mode, private notifications, etc
160    private boolean mLockscreenPublicMode = false;
161    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
162    private Context mLightThemeContext;
163    private ImageUtils mImageUtils = new ImageUtils();
164
165    private UserManager mUserManager;
166
167    // UI-specific methods
168
169    /**
170     * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
171     * and add them to the window manager.
172     */
173    protected abstract void createAndAddWindows();
174
175    protected WindowManager mWindowManager;
176    protected IWindowManager mWindowManagerService;
177    protected abstract void refreshLayout(int layoutDirection);
178
179    protected Display mDisplay;
180
181    private boolean mDeviceProvisioned = false;
182
183    private RecentsComponent mRecents;
184
185    protected int mZenMode;
186
187    public IStatusBarService getStatusBarService() {
188        return mBarService;
189    }
190
191    public boolean isDeviceProvisioned() {
192        return mDeviceProvisioned;
193    }
194
195    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
196        @Override
197        public void onChange(boolean selfChange) {
198            final boolean provisioned = 0 != Settings.Global.getInt(
199                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
200            if (provisioned != mDeviceProvisioned) {
201                mDeviceProvisioned = provisioned;
202                updateNotificationIcons();
203            }
204            final int mode = Settings.Global.getInt(mContext.getContentResolver(),
205                    Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
206            setZenMode(mode);
207        }
208    };
209
210    private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
211        @Override
212        public void onChange(boolean selfChange) {
213            // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
214            // so we just dump our cache ...
215            mUsersAllowingPrivateNotifications.clear();
216            // ... and refresh all the notifications
217            updateNotificationIcons();
218        }
219    };
220
221    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
222        @Override
223        public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
224            if (DEBUG) {
225                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
226            }
227            final boolean isActivity = pendingIntent.isActivity();
228            if (isActivity) {
229                try {
230                    // The intent we are sending is for the application, which
231                    // won't have permission to immediately start an activity after
232                    // the user switches to home.  We know it is safe to do at this
233                    // point, so make sure new activity switches are now allowed.
234                    ActivityManagerNative.getDefault().resumeAppSwitches();
235                    // Also, notifications can be launched from the lock screen,
236                    // so dismiss the lock screen when the activity starts.
237                    ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
238                } catch (RemoteException e) {
239                }
240            }
241
242            boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
243
244            if (isActivity && handled) {
245                // close the shade if it was open
246                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
247                visibilityChanged(false);
248            }
249            return handled;
250        }
251    };
252
253    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
254        @Override
255        public void onReceive(Context context, Intent intent) {
256            String action = intent.getAction();
257            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
258                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
259                updateRelatedUserCache();
260                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
261                userSwitched(mCurrentUserId);
262            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
263                updateRelatedUserCache();
264            }
265        }
266    };
267
268    private void updateRelatedUserCache() {
269        synchronized (mRelatedUsers) {
270            mRelatedUsers.clear();
271            if (mUserManager != null) {
272                for (UserInfo related : mUserManager.getRelatedUsers(mCurrentUserId)) {
273                    mRelatedUsers.put(related.id, related);
274                }
275            }
276        }
277    }
278
279    public void start() {
280        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
281        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
282        mDisplay = mWindowManager.getDefaultDisplay();
283
284        mDreamManager = IDreamManager.Stub.asInterface(
285                ServiceManager.checkService(DreamService.DREAM_SERVICE));
286        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
287
288        mSettingsObserver.onChange(false); // set up
289        mContext.getContentResolver().registerContentObserver(
290                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
291                mSettingsObserver);
292        mContext.getContentResolver().registerContentObserver(
293                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
294                mSettingsObserver);
295
296        mContext.getContentResolver().registerContentObserver(
297                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
298                true,
299                mLockscreenSettingsObserver,
300                UserHandle.USER_ALL);
301        mLightThemeContext = new RemoteViewsThemeContextWrapper(mContext,
302                android.R.style.Theme_Holo_Light);
303
304        mBarService = IStatusBarService.Stub.asInterface(
305                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
306
307        mRecents = getComponent(RecentsComponent.class);
308
309        mLocale = mContext.getResources().getConfiguration().locale;
310        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
311
312        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
313
314        // Connect in to the status bar manager service
315        StatusBarIconList iconList = new StatusBarIconList();
316        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
317        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
318        mCommandQueue = new CommandQueue(this, iconList);
319
320        int[] switches = new int[7];
321        ArrayList<IBinder> binders = new ArrayList<IBinder>();
322        try {
323            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
324                    switches, binders);
325        } catch (RemoteException ex) {
326            // If the system process isn't there we're doomed anyway.
327        }
328
329        createAndAddWindows();
330
331        disable(switches[0]);
332        setSystemUiVisibility(switches[1], 0xffffffff);
333        topAppWindowChanged(switches[2] != 0);
334        // StatusBarManagerService has a back up of IME token and it's restored here.
335        setImeWindowStatus(binders.get(0), switches[3], switches[4]);
336        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
337
338        // Set up the initial icon state
339        int N = iconList.size();
340        int viewIndex = 0;
341        for (int i=0; i<N; i++) {
342            StatusBarIcon icon = iconList.getIcon(i);
343            if (icon != null) {
344                addIcon(iconList.getSlot(i), i, viewIndex, icon);
345                viewIndex++;
346            }
347        }
348
349        // Set up the initial notification state
350        N = notificationKeys.size();
351        if (N == notifications.size()) {
352            for (int i=0; i<N; i++) {
353                addNotification(notificationKeys.get(i), notifications.get(i));
354            }
355        } else {
356            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
357                    + " notifications=" + notifications.size());
358        }
359
360        if (DEBUG) {
361            Log.d(TAG, String.format(
362                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
363                   iconList.size(),
364                   switches[0],
365                   switches[1],
366                   switches[2],
367                   switches[3]
368                   ));
369        }
370
371        mCurrentUserId = ActivityManager.getCurrentUser();
372
373        IntentFilter filter = new IntentFilter();
374        filter.addAction(Intent.ACTION_USER_SWITCHED);
375        filter.addAction(Intent.ACTION_USER_ADDED);
376        mContext.registerReceiver(mBroadcastReceiver, filter);
377
378        updateRelatedUserCache();
379    }
380
381    public void userSwitched(int newUserId) {
382        // should be overridden
383    }
384
385    public boolean notificationIsForCurrentOrRelatedUser(StatusBarNotification n) {
386        final int thisUserId = mCurrentUserId;
387        final int notificationUserId = n.getUserId();
388        if (DEBUG && MULTIUSER_DEBUG) {
389            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
390                    n, thisUserId, notificationUserId));
391        }
392        synchronized (mRelatedUsers) {
393            return notificationUserId == UserHandle.USER_ALL
394                    || thisUserId == notificationUserId
395                    || mRelatedUsers.get(notificationUserId) != null;
396        }
397    }
398
399    @Override
400    protected void onConfigurationChanged(Configuration newConfig) {
401        final Locale locale = mContext.getResources().getConfiguration().locale;
402        final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
403        if (! locale.equals(mLocale) || ld != mLayoutDirection) {
404            if (DEBUG) {
405                Log.v(TAG, String.format(
406                        "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
407                        locale, ld));
408            }
409            mLocale = locale;
410            mLayoutDirection = ld;
411            refreshLayout(ld);
412        }
413    }
414
415    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
416        View vetoButton = row.findViewById(R.id.veto);
417        if (n.isClearable() || (mInterruptingNotificationEntry != null
418                && mInterruptingNotificationEntry.row == row)) {
419            final String _pkg = n.getPackageName();
420            final String _tag = n.getTag();
421            final int _id = n.getId();
422            final int _userId = n.getUserId();
423            vetoButton.setOnClickListener(new View.OnClickListener() {
424                    public void onClick(View v) {
425                        // Accessibility feedback
426                        v.announceForAccessibility(
427                                mContext.getString(R.string.accessibility_notification_dismissed));
428                        try {
429                            mBarService.onNotificationClear(_pkg, _tag, _id, _userId);
430
431                        } catch (RemoteException ex) {
432                            // system process is dead if we're here.
433                        }
434                    }
435                });
436            vetoButton.setVisibility(View.VISIBLE);
437        } else {
438            vetoButton.setVisibility(View.GONE);
439        }
440        vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
441        return vetoButton;
442    }
443
444
445    protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
446        if (sbn.getNotification().contentView.getLayoutId() !=
447                com.android.internal.R.layout.notification_template_base) {
448            int version = 0;
449            try {
450                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
451                version = info.targetSdkVersion;
452            } catch (NameNotFoundException ex) {
453                Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
454            }
455            if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
456                content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
457            } else {
458                content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
459            }
460        }
461    }
462
463    private void processLegacyHoloNotification(StatusBarNotification sbn, View content) {
464
465        // TODO: Also skip processing if it is a holo-style notification.
466        // If the notification is custom, we can't process it.
467        if (!sbn.getNotification().extras.getBoolean(Notification.EXTRA_BUILDER_REMOTE_VIEWS)) {
468            return;
469        }
470
471        processLegacyHoloLargeIcon(content);
472        processLegacyHoloActions(content);
473        processLegacyNotificationIcon(content);
474        processLegacyTextViews(content);
475    }
476
477    /**
478     * @return the context to be used for the inflation of the specified {@code sbn}; this is
479     *      dependent whether the notification is quantum-style or holo-style
480     */
481    private Context getInflationContext(StatusBarNotification sbn) {
482
483        // TODO: Adjust this logic when we change the theme of the status bar windows.
484        if (sbn.getNotification().extras.getBoolean(Notification.EXTRA_BUILDER_REMOTE_VIEWS)) {
485            return mLightThemeContext;
486        } else {
487            return mContext;
488        }
489    }
490
491    private void processLegacyNotificationIcon(View content) {
492        View v = content.findViewById(com.android.internal.R.id.right_icon);
493        if (v != null & v instanceof ImageView) {
494            ImageView iv = (ImageView) v;
495            Drawable d = iv.getDrawable();
496            if (isMonochrome(d)) {
497                d.mutate();
498                d.setColorFilter(mLightThemeContext.getResources().getColor(
499                        R.color.notification_action_legacy_color_filter), PorterDuff.Mode.MULTIPLY);
500            }
501        }
502    }
503
504    private void processLegacyHoloLargeIcon(View content) {
505        View v = content.findViewById(com.android.internal.R.id.icon);
506        if (v != null & v instanceof ImageView) {
507            ImageView iv = (ImageView) v;
508            if (isMonochrome(iv.getDrawable())) {
509                iv.setBackground(mLightThemeContext.getResources().getDrawable(
510                        R.drawable.notification_icon_legacy_bg_inset));
511            }
512        }
513    }
514
515    private boolean isMonochrome(Drawable d) {
516        if (d == null) {
517            return false;
518        } else if (d instanceof BitmapDrawable) {
519            BitmapDrawable bd = (BitmapDrawable) d;
520            return bd.getBitmap() != null && mImageUtils.isGrayscale(bd.getBitmap());
521        } else if (d instanceof AnimationDrawable) {
522            AnimationDrawable ad = (AnimationDrawable) d;
523            int count = ad.getNumberOfFrames();
524            return count > 0 && isMonochrome(ad.getFrame(0));
525        } else {
526            return false;
527        }
528    }
529
530    private void processLegacyHoloActions(View content) {
531        View v = content.findViewById(com.android.internal.R.id.actions);
532        if (v != null & v instanceof ViewGroup) {
533            ViewGroup vg = (ViewGroup) v;
534            int childCount = vg.getChildCount();
535            for (int i = 0; i < childCount; i++) {
536                View child = vg.getChildAt(i);
537                if (child instanceof Button) {
538                    Button button = (Button) child;
539                    Drawable[] compoundDrawables = button.getCompoundDrawablesRelative();
540                    if (isMonochrome(compoundDrawables[0])) {
541                        Drawable d = compoundDrawables[0];
542                        d.mutate();
543                        d.setColorFilter(mLightThemeContext.getResources().getColor(
544                                R.color.notification_action_legacy_color_filter),
545                                PorterDuff.Mode.MULTIPLY);
546                    }
547                }
548            }
549        }
550    }
551
552    private void processLegacyTextViews(View content) {
553        Stack<View> viewStack = new Stack<View>();
554        viewStack.push(content);
555        while(!viewStack.isEmpty()) {
556            View current = viewStack.pop();
557            if(current instanceof ViewGroup){
558                ViewGroup currentGroup = (ViewGroup) current;
559                int numChildren = currentGroup.getChildCount();
560                for(int i=0;i<numChildren;i++){
561                    viewStack.push(currentGroup.getChildAt(i));
562                }
563            }
564            if (current instanceof TextView) {
565                processLegacyTextView((TextView) current);
566            }
567        }
568    }
569
570    private void processLegacyTextView(TextView textView) {
571        if (textView.getText() instanceof Spanned) {
572            Spanned ss = (Spanned) textView.getText();
573            Object[] spans = ss.getSpans(0, ss.length(), Object.class);
574            SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
575            for (Object span : spans) {
576                Object resultSpan = span;
577                if (span instanceof TextAppearanceSpan) {
578                    resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span);
579                }
580                builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
581                        ss.getSpanFlags(span));
582            }
583            textView.setText(builder);
584        }
585    }
586
587    private TextAppearanceSpan processTextAppearanceSpan(TextAppearanceSpan span) {
588        ColorStateList colorStateList = span.getTextColor();
589        if (colorStateList != null) {
590            int[] colors = colorStateList.getColors();
591            boolean changed = false;
592            for (int i = 0; i < colors.length; i++) {
593                if (mImageUtils.isGrayscale(colors[i])) {
594                    colors[i] = processColor(colors[i]);
595                    changed = true;
596                }
597            }
598            if (changed) {
599                return new TextAppearanceSpan(
600                        span.getFamily(), span.getTextStyle(), span.getTextSize(),
601                        new ColorStateList(colorStateList.getStates(), colors),
602                        span.getLinkTextColor());
603            }
604        }
605        return span;
606    }
607
608    private int processColor(int color) {
609        return Color.argb(Color.alpha(color),
610                255 - Color.red(color),
611                255 - Color.green(color),
612                255 - Color.blue(color));
613    }
614
615    private void startApplicationDetailsActivity(String packageName) {
616        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
617                Uri.fromParts("package", packageName, null));
618        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
619        TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
620                null, UserHandle.CURRENT);
621    }
622
623    protected View.OnLongClickListener getNotificationLongClicker() {
624        return new View.OnLongClickListener() {
625            @Override
626            public boolean onLongClick(View v) {
627                final String packageNameF = (String) v.getTag();
628                if (packageNameF == null) return false;
629                if (v.getWindowToken() == null) return false;
630                mNotificationBlamePopup = new PopupMenu(mContext, v);
631                mNotificationBlamePopup.getMenuInflater().inflate(
632                        R.menu.notification_popup_menu,
633                        mNotificationBlamePopup.getMenu());
634                mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
635                    public boolean onMenuItemClick(MenuItem item) {
636                        if (item.getItemId() == R.id.notification_inspect_item) {
637                            startApplicationDetailsActivity(packageNameF);
638                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
639                        } else {
640                            return false;
641                        }
642                        return true;
643                    }
644                });
645                mNotificationBlamePopup.show();
646
647                return true;
648            }
649        };
650    }
651
652    public void dismissPopups() {
653        if (mNotificationBlamePopup != null) {
654            mNotificationBlamePopup.dismiss();
655            mNotificationBlamePopup = null;
656        }
657    }
658
659    public void onHeadsUpDismissed() {
660    }
661
662    @Override
663    public void toggleRecentApps() {
664        int msg = MSG_TOGGLE_RECENTS_PANEL;
665        mHandler.removeMessages(msg);
666        mHandler.sendEmptyMessage(msg);
667    }
668
669    @Override
670    public void preloadRecentApps() {
671        int msg = MSG_PRELOAD_RECENT_APPS;
672        mHandler.removeMessages(msg);
673        mHandler.sendEmptyMessage(msg);
674    }
675
676    @Override
677    public void cancelPreloadRecentApps() {
678        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
679        mHandler.removeMessages(msg);
680        mHandler.sendEmptyMessage(msg);
681    }
682
683    @Override
684    public void showSearchPanel() {
685        int msg = MSG_OPEN_SEARCH_PANEL;
686        mHandler.removeMessages(msg);
687        mHandler.sendEmptyMessage(msg);
688    }
689
690    @Override
691    public void hideSearchPanel() {
692        int msg = MSG_CLOSE_SEARCH_PANEL;
693        mHandler.removeMessages(msg);
694        mHandler.sendEmptyMessage(msg);
695    }
696
697    protected abstract WindowManager.LayoutParams getSearchLayoutParams(
698            LayoutParams layoutParams);
699
700    protected void updateSearchPanel() {
701        // Search Panel
702        boolean visible = false;
703        if (mSearchPanelView != null) {
704            visible = mSearchPanelView.isShowing();
705            mWindowManager.removeView(mSearchPanelView);
706        }
707
708        // Provide SearchPanel with a temporary parent to allow layout params to work.
709        LinearLayout tmpRoot = new LinearLayout(mContext);
710        mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
711                 R.layout.status_bar_search_panel, tmpRoot, false);
712        mSearchPanelView.setOnTouchListener(
713                 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
714        mSearchPanelView.setVisibility(View.GONE);
715
716        WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
717
718        mWindowManager.addView(mSearchPanelView, lp);
719        mSearchPanelView.setBar(this);
720        if (visible) {
721            mSearchPanelView.show(true, false);
722        }
723    }
724
725    protected H createHandler() {
726         return new H();
727    }
728
729    static void sendCloseSystemWindows(Context context, String reason) {
730        if (ActivityManagerNative.isSystemReady()) {
731            try {
732                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
733            } catch (RemoteException e) {
734            }
735        }
736    }
737
738    protected abstract View getStatusBarView();
739
740    protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
741        // additional optimization when we have software system buttons - start loading the recent
742        // tasks on touch down
743        @Override
744        public boolean onTouch(View v, MotionEvent event) {
745            int action = event.getAction() & MotionEvent.ACTION_MASK;
746            if (action == MotionEvent.ACTION_DOWN) {
747                preloadRecentTasksList();
748            } else if (action == MotionEvent.ACTION_CANCEL) {
749                cancelPreloadingRecentTasksList();
750            } else if (action == MotionEvent.ACTION_UP) {
751                if (!v.isPressed()) {
752                    cancelPreloadingRecentTasksList();
753                }
754
755            }
756            return false;
757        }
758    };
759
760    protected void toggleRecentsActivity() {
761        if (mRecents != null) {
762            sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
763            mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
764        }
765    }
766
767    protected void preloadRecentTasksList() {
768        if (mRecents != null) {
769            mRecents.preloadRecentTasksList();
770        }
771    }
772
773    protected void cancelPreloadingRecentTasksList() {
774        if (mRecents != null) {
775            mRecents.cancelPreloadingRecentTasksList();
776        }
777    }
778
779    protected void closeRecents() {
780        if (mRecents != null) {
781            mRecents.closeRecents();
782        }
783    }
784
785    public abstract void resetHeadsUpDecayTimer();
786
787    /**
788     * Save the current "public" (locked and secure) state of the lockscreen.
789     */
790    public void setLockscreenPublicMode(boolean publicMode) {
791        mLockscreenPublicMode = publicMode;
792    }
793
794    public boolean isLockscreenPublicMode() {
795        return mLockscreenPublicMode;
796    }
797
798    /**
799     * Has the given user chosen to allow their private (full) notifications to be shown even
800     * when the lockscreen is in "public" (secure & locked) mode?
801     */
802    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
803        if (userHandle == UserHandle.USER_ALL) {
804            return true;
805        }
806
807        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
808            final boolean allowed = 0 != Settings.Secure.getIntForUser(
809                    mContext.getContentResolver(),
810                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
811            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
812            return allowed;
813        }
814
815        return mUsersAllowingPrivateNotifications.get(userHandle);
816    }
817
818    protected class H extends Handler {
819        public void handleMessage(Message m) {
820            Intent intent;
821            switch (m.what) {
822             case MSG_TOGGLE_RECENTS_PANEL:
823                 toggleRecentsActivity();
824                 break;
825             case MSG_CLOSE_RECENTS_PANEL:
826                 closeRecents();
827                 break;
828             case MSG_PRELOAD_RECENT_APPS:
829                  preloadRecentTasksList();
830                  break;
831             case MSG_CANCEL_PRELOAD_RECENT_APPS:
832                  cancelPreloadingRecentTasksList();
833                  break;
834             case MSG_OPEN_SEARCH_PANEL:
835                 if (DEBUG) Log.d(TAG, "opening search panel");
836                 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
837                     mSearchPanelView.show(true, true);
838                     onShowSearchPanel();
839                 }
840                 break;
841             case MSG_CLOSE_SEARCH_PANEL:
842                 if (DEBUG) Log.d(TAG, "closing search panel");
843                 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
844                     mSearchPanelView.show(false, true);
845                     onHideSearchPanel();
846                 }
847                 break;
848            }
849        }
850    }
851
852    public class TouchOutsideListener implements View.OnTouchListener {
853        private int mMsg;
854        private StatusBarPanel mPanel;
855
856        public TouchOutsideListener(int msg, StatusBarPanel panel) {
857            mMsg = msg;
858            mPanel = panel;
859        }
860
861        public boolean onTouch(View v, MotionEvent ev) {
862            final int action = ev.getAction();
863            if (action == MotionEvent.ACTION_OUTSIDE
864                || (action == MotionEvent.ACTION_DOWN
865                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
866                mHandler.removeMessages(mMsg);
867                mHandler.sendEmptyMessage(mMsg);
868                return true;
869            }
870            return false;
871        }
872    }
873
874    protected void workAroundBadLayerDrawableOpacity(View v) {
875    }
876
877    protected void onHideSearchPanel() {
878    }
879
880    protected void onShowSearchPanel() {
881    }
882
883    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
884            return inflateViews(entry, parent, false);
885    }
886
887    public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
888            return inflateViews(entry, parent, true);
889    }
890
891    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
892        int minHeight =
893                mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
894        int maxHeight =
895                mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
896        StatusBarNotification sbn = entry.notification;
897        RemoteViews contentView = sbn.getNotification().contentView;
898        RemoteViews bigContentView = sbn.getNotification().bigContentView;
899
900        if (isHeadsUp) {
901            maxHeight =
902                    mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
903            bigContentView = sbn.getNotification().headsUpContentView;
904        }
905
906        if (contentView == null) {
907            return false;
908        }
909
910        Log.v(TAG, "publicNotification: "
911                + sbn.getNotification().publicVersion);
912
913        Notification publicNotification = sbn.getNotification().publicVersion;
914
915        // create the row view
916        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
917                Context.LAYOUT_INFLATER_SERVICE);
918        ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
919                R.layout.status_bar_notification_row, parent, false);
920
921        // for blaming (see SwipeHelper.setLongPressListener)
922        row.setTag(sbn.getPackageName());
923
924        workAroundBadLayerDrawableOpacity(row);
925        View vetoButton = updateNotificationVetoButton(row, sbn);
926        vetoButton.setContentDescription(mContext.getString(
927                R.string.accessibility_remove_notification));
928
929        // NB: the large icon is now handled entirely by the template
930
931        // bind the click event to the content area
932        ViewGroup content = (ViewGroup)row.findViewById(R.id.container);
933        SizeAdaptiveLayout expanded = (SizeAdaptiveLayout)row.findViewById(R.id.expanded);
934        SizeAdaptiveLayout expandedPublic
935                = (SizeAdaptiveLayout)row.findViewById(R.id.expandedPublic);
936
937        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
938
939        PendingIntent contentIntent = sbn.getNotification().contentIntent;
940        if (contentIntent != null) {
941            final View.OnClickListener listener = makeClicker(contentIntent,
942                    sbn.getPackageName(), sbn.getTag(), sbn.getId(), isHeadsUp, sbn.getUserId());
943            content.setOnClickListener(listener);
944        } else {
945            content.setOnClickListener(null);
946        }
947
948        // set up the adaptive layout
949        View contentViewLocal = null;
950        View bigContentViewLocal = null;
951        try {
952            contentViewLocal = contentView.apply(getInflationContext(sbn), expanded,
953                    mOnClickHandler);
954            if (bigContentView != null) {
955                bigContentViewLocal = bigContentView.apply(getInflationContext(sbn), expanded,
956                        mOnClickHandler);
957            }
958        }
959        catch (RuntimeException e) {
960            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
961            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
962            return false;
963        }
964
965        if (contentViewLocal != null) {
966            contentViewLocal.setIsRootNamespace(true);
967            SizeAdaptiveLayout.LayoutParams params =
968                    new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
969            params.minHeight = minHeight;
970            params.maxHeight = minHeight;
971            expanded.addView(contentViewLocal, params);
972        }
973        if (bigContentViewLocal != null) {
974            bigContentViewLocal.setIsRootNamespace(true);
975            SizeAdaptiveLayout.LayoutParams params =
976                    new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
977            params.minHeight = minHeight+1;
978            params.maxHeight = maxHeight;
979            expanded.addView(bigContentViewLocal, params);
980        }
981
982        PackageManager pm = mContext.getPackageManager();
983
984        // now the public version
985        View publicViewLocal = null;
986        if (publicNotification != null) {
987            try {
988                publicViewLocal = publicNotification.contentView.apply(getInflationContext(sbn),
989                        expandedPublic, mOnClickHandler);
990
991                if (publicViewLocal != null) {
992                    publicViewLocal.setIsRootNamespace(true);
993                    SizeAdaptiveLayout.LayoutParams params =
994                            new SizeAdaptiveLayout.LayoutParams(publicViewLocal.getLayoutParams());
995                    params.minHeight = minHeight;
996                    params.maxHeight = minHeight;
997                    expandedPublic.addView(publicViewLocal, params);
998                }
999            }
1000            catch (RuntimeException e) {
1001                final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
1002                Log.e(TAG, "couldn't inflate public view for notification " + ident, e);
1003                publicViewLocal = null;
1004            }
1005        }
1006
1007        if (publicViewLocal == null) {
1008            // Add a basic notification template
1009            publicViewLocal = LayoutInflater.from(mContext).inflate(
1010                    com.android.internal.R.layout.notification_template_base, expandedPublic, true);
1011
1012            final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title);
1013            try {
1014                title.setText(pm.getApplicationLabel(
1015                        pm.getApplicationInfo(entry.notification.getPackageName(), 0)));
1016            } catch (NameNotFoundException e) {
1017                title.setText(entry.notification.getPackageName());
1018            }
1019
1020            final ImageView icon = (ImageView) publicViewLocal.findViewById(com.android.internal.R.id.icon);
1021
1022            final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(),
1023                    entry.notification.getUser(),
1024                    entry.notification.getNotification().icon,
1025                    entry.notification.getNotification().iconLevel,
1026                    entry.notification.getNotification().number,
1027                    entry.notification.getNotification().tickerText);
1028
1029            icon.setImageDrawable(StatusBarIconView.getIcon(mContext, ic));
1030
1031            final TextView text = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.text);
1032            text.setText("Unlock your device to see this notification.");
1033
1034            // TODO: fill out "time" as well
1035        }
1036
1037        row.setDrawingCacheEnabled(true);
1038
1039        applyLegacyRowBackground(sbn, content);
1040        processLegacyHoloNotification(sbn, contentViewLocal);
1041        if (bigContentViewLocal != null) {
1042            processLegacyHoloNotification(sbn, bigContentViewLocal);
1043        }
1044        if (publicViewLocal != null) {
1045            processLegacyHoloNotification(sbn, publicViewLocal);
1046        }
1047
1048        if (MULTIUSER_DEBUG) {
1049            TextView debug = (TextView) row.findViewById(R.id.debug_info);
1050            if (debug != null) {
1051                debug.setVisibility(View.VISIBLE);
1052                debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId());
1053            }
1054        }
1055        entry.row = row;
1056        entry.row.setRowHeight(mRowHeight);
1057        entry.content = content;
1058        entry.expanded = contentViewLocal;
1059        entry.expandedPublic = publicViewLocal;
1060        entry.setBigContentView(bigContentViewLocal);
1061
1062        return true;
1063    }
1064
1065    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag,
1066            int id, boolean forHun, int userId) {
1067        return new NotificationClicker(intent, pkg, tag, id, forHun, userId);
1068    }
1069
1070    protected class NotificationClicker implements View.OnClickListener {
1071        private PendingIntent mIntent;
1072        private String mPkg;
1073        private String mTag;
1074        private int mId;
1075        private boolean mIsHeadsUp;
1076        private int mUserId;
1077
1078        public NotificationClicker(PendingIntent intent, String pkg, String tag, int id,
1079                boolean forHun, int userId) {
1080            mIntent = intent;
1081            mPkg = pkg;
1082            mTag = tag;
1083            mId = id;
1084            mIsHeadsUp = forHun;
1085            mUserId = userId;
1086        }
1087
1088        public void onClick(View v) {
1089            try {
1090                // The intent we are sending is for the application, which
1091                // won't have permission to immediately start an activity after
1092                // the user switches to home.  We know it is safe to do at this
1093                // point, so make sure new activity switches are now allowed.
1094                ActivityManagerNative.getDefault().resumeAppSwitches();
1095                // Also, notifications can be launched from the lock screen,
1096                // so dismiss the lock screen when the activity starts.
1097                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
1098            } catch (RemoteException e) {
1099            }
1100
1101            if (mIntent != null) {
1102                int[] pos = new int[2];
1103                v.getLocationOnScreen(pos);
1104                Intent overlay = new Intent();
1105                overlay.setSourceBounds(
1106                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
1107                try {
1108                    mIntent.send(mContext, 0, overlay);
1109                } catch (PendingIntent.CanceledException e) {
1110                    // the stack trace isn't very helpful here.  Just log the exception message.
1111                    Log.w(TAG, "Sending contentIntent failed: " + e);
1112                }
1113
1114                KeyguardTouchDelegate.getInstance(mContext).dismiss();
1115            }
1116
1117            try {
1118                if (mIsHeadsUp) {
1119                    mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
1120                }
1121                mBarService.onNotificationClick(mPkg, mTag, mId, mUserId);
1122            } catch (RemoteException ex) {
1123                // system process is dead if we're here.
1124            }
1125
1126            // close the shade if it was open
1127            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
1128            visibilityChanged(false);
1129        }
1130    }
1131
1132    /**
1133     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
1134     * This was added last-minute and is inconsistent with the way the rest of the notifications
1135     * are handled, because the notification isn't really cancelled.  The lights are just
1136     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
1137     * this is what he wants. (see bug 1131461)
1138     */
1139    protected void visibilityChanged(boolean visible) {
1140        if (mPanelSlightlyVisible != visible) {
1141            mPanelSlightlyVisible = visible;
1142            try {
1143                mBarService.onPanelRevealed();
1144            } catch (RemoteException ex) {
1145                // Won't fail unless the world has ended.
1146            }
1147        }
1148    }
1149
1150    /**
1151     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
1152     * about the failure.
1153     *
1154     * WARNING: this will call back into us.  Don't hold any locks.
1155     */
1156    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
1157        removeNotification(key);
1158        try {
1159            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
1160                    n.getInitialPid(), message, n.getUserId());
1161        } catch (RemoteException ex) {
1162            // The end is nigh.
1163        }
1164    }
1165
1166    protected StatusBarNotification removeNotificationViews(IBinder key) {
1167        NotificationData.Entry entry = mNotificationData.remove(key);
1168        if (entry == null) {
1169            Log.w(TAG, "removeNotification for unknown key: " + key);
1170            return null;
1171        }
1172        // Remove the expanded view.
1173        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
1174        if (rowParent != null) rowParent.removeView(entry.row);
1175        updateExpansionStates();
1176        updateNotificationIcons();
1177
1178        return entry.notification;
1179    }
1180
1181    protected NotificationData.Entry createNotificationViews(IBinder key,
1182            StatusBarNotification notification) {
1183        if (DEBUG) {
1184            Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification);
1185        }
1186        // Construct the icon.
1187        final StatusBarIconView iconView = new StatusBarIconView(mContext,
1188                notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()),
1189                notification.getNotification());
1190        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1191
1192        final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
1193                notification.getUser(),
1194                    notification.getNotification().icon,
1195                    notification.getNotification().iconLevel,
1196                    notification.getNotification().number,
1197                    notification.getNotification().tickerText);
1198        if (!iconView.set(ic)) {
1199            handleNotificationError(key, notification, "Couldn't create icon: " + ic);
1200            return null;
1201        }
1202        // Construct the expanded view.
1203        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
1204        if (!inflateViews(entry, mPile)) {
1205            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
1206                    + notification);
1207            return null;
1208        }
1209        return entry;
1210    }
1211
1212    protected void addNotificationViews(NotificationData.Entry entry) {
1213        // Add the expanded view and icon.
1214        int pos = mNotificationData.add(entry);
1215        if (DEBUG) {
1216            Log.d(TAG, "addNotificationViews: added at " + pos);
1217        }
1218        updateInterceptedState(entry);
1219        updateExpansionStates();
1220        updateNotificationIcons();
1221    }
1222
1223    private void addNotificationViews(IBinder key, StatusBarNotification notification) {
1224        addNotificationViews(createNotificationViews(key, notification));
1225    }
1226
1227    protected void updateExpansionStates() {
1228        int N = mNotificationData.size();
1229        for (int i = 0; i < N; i++) {
1230            NotificationData.Entry entry = mNotificationData.get(i);
1231            if (!entry.row.isUserLocked()) {
1232                if (i == (N-1)) {
1233                    if (DEBUG) Log.d(TAG, "expanding top notification at " + i);
1234                    entry.row.setExpanded(true);
1235                } else {
1236                    if (!entry.row.isUserExpanded()) {
1237                        if (DEBUG) Log.d(TAG, "collapsing notification at " + i);
1238                        entry.row.setExpanded(false);
1239                    } else {
1240                        if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i);
1241                    }
1242                }
1243            } else {
1244                if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i);
1245            }
1246        }
1247    }
1248
1249    protected void setZenMode(int mode) {
1250        if (!isDeviceProvisioned()) return;
1251        final boolean change = mZenMode != mode;
1252        mZenMode = mode;
1253        final int N = mNotificationData.size();
1254        for (int i = 0; i < N; i++) {
1255            final NotificationData.Entry entry = mNotificationData.get(i);
1256            if (change && !shouldIntercept()) {
1257                entry.notification.getNotification().extras.putBoolean(EXTRA_INTERCEPT, false);
1258            }
1259            updateInterceptedState(entry);
1260        }
1261        updateNotificationIcons();
1262    }
1263
1264    private boolean shouldIntercept() {
1265        return mZenMode != Settings.Global.ZEN_MODE_OFF;
1266    }
1267
1268    protected boolean shouldIntercept(Notification n) {
1269        return shouldIntercept() && n.extras.getBoolean(EXTRA_INTERCEPT);
1270    }
1271
1272    private void updateInterceptedState(NotificationData.Entry entry) {
1273        final boolean intercepted = shouldIntercept(entry.notification.getNotification());
1274        entry.row.findViewById(R.id.container).setAlpha(intercepted ? INTERCEPTED_ALPHA : 1);
1275    }
1276
1277    protected abstract void haltTicker();
1278    protected abstract void setAreThereNotifications();
1279    protected abstract void updateNotificationIcons();
1280    protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
1281    protected abstract void updateExpandedViewPos(int expandedPosition);
1282    protected abstract int getExpandedViewMaxHeight();
1283    protected abstract boolean shouldDisableNavbarGestures();
1284
1285    protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
1286        return parent != null && parent.indexOfChild(entry.row) == 0;
1287    }
1288
1289    public void updateNotification(IBinder key, StatusBarNotification notification) {
1290        if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
1291
1292        final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
1293        if (oldEntry == null) {
1294            Log.w(TAG, "updateNotification for unknown key: " + key);
1295            return;
1296        }
1297
1298        final StatusBarNotification oldNotification = oldEntry.notification;
1299
1300        // XXX: modify when we do something more intelligent with the two content views
1301        final RemoteViews oldContentView = oldNotification.getNotification().contentView;
1302        final RemoteViews contentView = notification.getNotification().contentView;
1303        final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
1304        final RemoteViews bigContentView = notification.getNotification().bigContentView;
1305        final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView;
1306        final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
1307        final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
1308        final RemoteViews oldPublicContentView = oldPublicNotification != null
1309                ? oldPublicNotification.contentView : null;
1310        final Notification publicNotification = notification.getNotification().publicVersion;
1311        final RemoteViews publicContentView = publicNotification != null
1312                ? publicNotification.contentView : null;
1313
1314        if (DEBUG) {
1315            Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
1316                    + " ongoing=" + oldNotification.isOngoing()
1317                    + " expanded=" + oldEntry.expanded
1318                    + " contentView=" + oldContentView
1319                    + " bigContentView=" + oldBigContentView
1320                    + " publicView=" + oldPublicContentView
1321                    + " rowParent=" + oldEntry.row.getParent());
1322            Log.d(TAG, "new notification: when=" + notification.getNotification().when
1323                    + " ongoing=" + oldNotification.isOngoing()
1324                    + " contentView=" + contentView
1325                    + " bigContentView=" + bigContentView
1326                    + " publicView=" + publicContentView);
1327        }
1328
1329        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
1330        // didn't change.
1331
1332        // 1U is never null
1333        boolean contentsUnchanged = oldEntry.expanded != null
1334                && contentView.getPackage() != null
1335                && oldContentView.getPackage() != null
1336                && oldContentView.getPackage().equals(contentView.getPackage())
1337                && oldContentView.getLayoutId() == contentView.getLayoutId();
1338        // large view may be null
1339        boolean bigContentsUnchanged =
1340                (oldEntry.getBigContentView() == null && bigContentView == null)
1341                || ((oldEntry.getBigContentView() != null && bigContentView != null)
1342                    && bigContentView.getPackage() != null
1343                    && oldBigContentView.getPackage() != null
1344                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
1345                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
1346        boolean headsUpContentsUnchanged =
1347                (oldHeadsUpContentView == null && headsUpContentView == null)
1348                || ((oldHeadsUpContentView != null && headsUpContentView != null)
1349                    && headsUpContentView.getPackage() != null
1350                    && oldHeadsUpContentView.getPackage() != null
1351                    && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage())
1352                    && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId());
1353        boolean publicUnchanged  =
1354                (oldPublicContentView == null && publicContentView == null)
1355                || ((oldPublicContentView != null && publicContentView != null)
1356                        && publicContentView.getPackage() != null
1357                        && oldPublicContentView.getPackage() != null
1358                        && oldPublicContentView.getPackage().equals(publicContentView.getPackage())
1359                        && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
1360
1361        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
1362        boolean orderUnchanged =
1363                   notification.getNotification().when == oldNotification.getNotification().when
1364                && notification.getScore() == oldNotification.getScore();
1365                // score now encompasses/supersedes isOngoing()
1366
1367        boolean updateTicker = notification.getNotification().tickerText != null
1368                && !TextUtils.equals(notification.getNotification().tickerText,
1369                        oldEntry.notification.getNotification().tickerText);
1370        boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
1371        if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged && publicUnchanged
1372                && (orderUnchanged || isTopAnyway)) {
1373            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
1374            oldEntry.notification = notification;
1375            try {
1376                updateNotificationViews(oldEntry, notification);
1377
1378                if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
1379                        && oldNotification == mInterruptingNotificationEntry.notification) {
1380                    if (!shouldInterrupt(notification)) {
1381                        if (DEBUG) Log.d(TAG, "no longer interrupts!");
1382                        mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
1383                    } else {
1384                        if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
1385                        mInterruptingNotificationEntry.notification = notification;
1386                        updateHeadsUpViews(mInterruptingNotificationEntry, notification);
1387                    }
1388                }
1389
1390                // Update the icon.
1391                final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
1392                        notification.getUser(),
1393                        notification.getNotification().icon, notification.getNotification().iconLevel,
1394                        notification.getNotification().number,
1395                        notification.getNotification().tickerText);
1396                if (!oldEntry.icon.set(ic)) {
1397                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
1398                    return;
1399                }
1400                updateExpansionStates();
1401            }
1402            catch (RuntimeException e) {
1403                // It failed to add cleanly.  Log, and remove the view from the panel.
1404                Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
1405                removeNotificationViews(key);
1406                addNotificationViews(key, notification);
1407            }
1408        } else {
1409            if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
1410            if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
1411            if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
1412            if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
1413            final boolean wasExpanded = oldEntry.row.isUserExpanded();
1414            removeNotificationViews(key);
1415            addNotificationViews(key, notification);  // will also replace the heads up
1416            if (wasExpanded) {
1417                final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
1418                newEntry.row.setExpanded(true);
1419                newEntry.row.setUserExpanded(true);
1420            }
1421        }
1422
1423        // Update the veto button accordingly (and as a result, whether this row is
1424        // swipe-dismissable)
1425        updateNotificationVetoButton(oldEntry.row, notification);
1426
1427        // Is this for you?
1428        boolean isForCurrentUser = notificationIsForCurrentOrRelatedUser(notification);
1429        if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
1430
1431        // Restart the ticker if it's still running
1432        if (updateTicker && isForCurrentUser) {
1433            haltTicker();
1434            tick(key, notification, false);
1435        }
1436
1437        // Recalculate the position of the sliding windows and the titles.
1438        setAreThereNotifications();
1439        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1440    }
1441
1442    private void updateNotificationViews(NotificationData.Entry entry,
1443            StatusBarNotification notification) {
1444        updateNotificationViews(entry, notification, false);
1445    }
1446
1447    private void updateHeadsUpViews(NotificationData.Entry entry,
1448            StatusBarNotification notification) {
1449        updateNotificationViews(entry, notification, true);
1450    }
1451
1452    private void updateNotificationViews(NotificationData.Entry entry,
1453            StatusBarNotification notification, boolean isHeadsUp) {
1454        final RemoteViews contentView = notification.getNotification().contentView;
1455        final RemoteViews bigContentView = isHeadsUp
1456                ? notification.getNotification().headsUpContentView
1457                : notification.getNotification().bigContentView;
1458        final Notification publicVersion = notification.getNotification().publicVersion;
1459        final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
1460                : null;
1461
1462        // Reapply the RemoteViews
1463        contentView.reapply(getInflationContext(notification), entry.expanded, mOnClickHandler);
1464        processLegacyHoloNotification(notification, entry.expanded);
1465        if (bigContentView != null && entry.getBigContentView() != null) {
1466            bigContentView.reapply(getInflationContext(notification), entry.getBigContentView(),
1467                    mOnClickHandler);
1468            processLegacyHoloNotification(notification, entry.getBigContentView());
1469        }
1470        if (publicContentView != null && entry.getPublicContentView() != null) {
1471            publicContentView.reapply(getInflationContext(notification),
1472                    entry.getPublicContentView(), mOnClickHandler);
1473            processLegacyHoloNotification(notification, entry.getPublicContentView());
1474        }
1475        // update the contentIntent
1476        final PendingIntent contentIntent = notification.getNotification().contentIntent;
1477        if (contentIntent != null) {
1478            final View.OnClickListener listener = makeClicker(contentIntent,
1479                    notification.getPackageName(), notification.getTag(), notification.getId(),
1480                    isHeadsUp, notification.getUserId());
1481            entry.content.setOnClickListener(listener);
1482        } else {
1483            entry.content.setOnClickListener(null);
1484        }
1485        updateInterceptedState(entry);
1486    }
1487
1488    protected void notifyHeadsUpScreenOn(boolean screenOn) {
1489        if (!screenOn && mInterruptingNotificationEntry != null) {
1490            mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
1491        }
1492    }
1493
1494    protected boolean shouldInterrupt(StatusBarNotification sbn) {
1495        Notification notification = sbn.getNotification();
1496        // some predicates to make the boolean logic legible
1497        boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
1498                || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
1499                || notification.sound != null
1500                || notification.vibrate != null;
1501        boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
1502        boolean isFullscreen = notification.fullScreenIntent != null;
1503        boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
1504        boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
1505                Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
1506
1507        final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
1508        boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
1509                && isAllowed
1510                && mPowerManager.isScreenOn()
1511                && !keyguard.isShowingAndNotHidden()
1512                && !keyguard.isInputRestricted();
1513        try {
1514            interrupt = interrupt && !mDreamManager.isDreaming();
1515        } catch (RemoteException e) {
1516            Log.d(TAG, "failed to query dream manager", e);
1517        }
1518        if (DEBUG) Log.d(TAG, "interrupt: " + interrupt);
1519        return interrupt;
1520    }
1521
1522    // Q: What kinds of notifications should show during setup?
1523    // A: Almost none! Only things coming from the system (package is "android") that also
1524    // have special "kind" tags marking them as relevant for setup (see below).
1525    protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
1526        return "android".equals(sbn.getPackageName())
1527                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
1528    }
1529
1530    public boolean inKeyguardRestrictedInputMode() {
1531        return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
1532    }
1533
1534    public void setInteracting(int barWindow, boolean interacting) {
1535        // hook for subclasses
1536    }
1537
1538    public void destroy() {
1539        if (mSearchPanelView != null) {
1540            mWindowManager.removeViewImmediate(mSearchPanelView);
1541        }
1542        mContext.unregisterReceiver(mBroadcastReceiver);
1543    }
1544
1545    /**
1546     * A custom context theme wrapper that applies a platform theme to a created package context.
1547     * This is useful if you want to inflate {@link RemoteViews} with a custom theme (normally, the
1548     * theme used there is the default platform theme).
1549     */
1550    private static class RemoteViewsThemeContextWrapper extends ContextThemeWrapper {
1551
1552        private int mThemeRes;
1553
1554        private RemoteViewsThemeContextWrapper(Context base, int themeres) {
1555            super(base, themeres);
1556            mThemeRes = themeres;
1557        }
1558
1559        @Override
1560        public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
1561                throws NameNotFoundException {
1562            Context c = super.createPackageContextAsUser(packageName, flags, user);
1563            c.setTheme(mThemeRes);
1564            return c;
1565        }
1566
1567        @Override
1568        public Context createPackageContext(String packageName, int flags)
1569                throws NameNotFoundException {
1570            Context c = super.createPackageContext(packageName, flags);
1571            c.setTheme(mThemeRes);
1572            return c;
1573        }
1574    }
1575}
1576