StatusBarManagerService.java revision 665366a3055c7baa36242b8c92c5ff85b1c3394b
1/*
2 * Copyright (C) 2007 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.server.statusbar;
18
19import android.app.StatusBarManager;
20import android.os.Binder;
21import android.os.Handler;
22import android.os.IBinder;
23import android.os.RemoteException;
24import android.os.UserHandle;
25import android.service.notification.StatusBarNotification;
26import android.content.Context;
27import android.content.pm.PackageManager;
28import android.content.res.Resources;
29import android.util.Slog;
30
31import com.android.internal.statusbar.IStatusBar;
32import com.android.internal.statusbar.IStatusBarService;
33import com.android.internal.statusbar.StatusBarIcon;
34import com.android.internal.statusbar.StatusBarIconList;
35import com.android.server.LocalServices;
36import com.android.server.notification.NotificationDelegate;
37import com.android.server.wm.WindowManagerService;
38
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.List;
44import java.util.Map;
45
46
47/**
48 * A note on locking:  We rely on the fact that calls onto mBar are oneway or
49 * if they are local, that they just enqueue messages to not deadlock.
50 */
51public class StatusBarManagerService extends IStatusBarService.Stub {
52    private static final String TAG = "StatusBarManagerService";
53    private static final boolean SPEW = false;
54
55    private final Context mContext;
56    private final WindowManagerService mWindowManager;
57    private Handler mHandler = new Handler();
58    private NotificationDelegate mNotificationDelegate;
59    private volatile IStatusBar mBar;
60    private StatusBarIconList mIcons = new StatusBarIconList();
61
62    // for disabling the status bar
63    private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
64    private IBinder mSysUiVisToken = new Binder();
65    private int mDisabled = 0;
66
67    private Object mLock = new Object();
68    // encompasses lights-out mode and other flags defined on View
69    private int mSystemUiVisibility = 0;
70    private boolean mMenuVisible = false;
71    private int mImeWindowVis = 0;
72    private int mImeBackDisposition;
73    private boolean mShowImeSwitcher;
74    private IBinder mImeToken = null;
75    private int mCurrentUserId;
76
77    private class DisableRecord implements IBinder.DeathRecipient {
78        int userId;
79        String pkg;
80        int what;
81        IBinder token;
82
83        public void binderDied() {
84            Slog.i(TAG, "binder died for pkg=" + pkg);
85            disableInternal(userId, 0, token, pkg);
86            token.unlinkToDeath(this, 0);
87        }
88    }
89
90    /**
91     * Construct the service, add the status bar view to the window manager
92     */
93    public StatusBarManagerService(Context context, WindowManagerService windowManager) {
94        mContext = context;
95        mWindowManager = windowManager;
96
97        final Resources res = context.getResources();
98        mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));
99
100        LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
101    }
102
103    /**
104     * Private API used by NotificationManagerService.
105     */
106    private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
107        private boolean mNotificationLightOn;
108
109        @Override
110        public void setNotificationDelegate(NotificationDelegate delegate) {
111            mNotificationDelegate = delegate;
112        }
113
114        @Override
115        public void buzzBeepBlinked() {
116            if (mBar != null) {
117                try {
118                    mBar.buzzBeepBlinked();
119                } catch (RemoteException ex) {
120                }
121            }
122        }
123
124        @Override
125        public void notificationLightPulse(int argb, int onMillis, int offMillis) {
126            mNotificationLightOn = true;
127            if (mBar != null) {
128                try {
129                    mBar.notificationLightPulse(argb, onMillis, offMillis);
130                } catch (RemoteException ex) {
131                }
132            }
133        }
134
135        @Override
136        public void notificationLightOff() {
137            if (mNotificationLightOn) {
138                mNotificationLightOn = false;
139                if (mBar != null) {
140                    try {
141                        mBar.notificationLightOff();
142                    } catch (RemoteException ex) {
143                    }
144                }
145            }
146        }
147    };
148
149    // ================================================================================
150    // From IStatusBarService
151    // ================================================================================
152    @Override
153    public void expandNotificationsPanel() {
154        enforceExpandStatusBar();
155
156        if (mBar != null) {
157            try {
158                mBar.animateExpandNotificationsPanel();
159            } catch (RemoteException ex) {
160            }
161        }
162    }
163
164    @Override
165    public void collapsePanels() {
166        enforceExpandStatusBar();
167
168        if (mBar != null) {
169            try {
170                mBar.animateCollapsePanels();
171            } catch (RemoteException ex) {
172            }
173        }
174    }
175
176    @Override
177    public void expandSettingsPanel() {
178        enforceExpandStatusBar();
179
180        if (mBar != null) {
181            try {
182                mBar.animateExpandSettingsPanel();
183            } catch (RemoteException ex) {
184            }
185        }
186    }
187
188    @Override
189    public void disable(int what, IBinder token, String pkg) {
190        if (!mNotificationDelegate.allowDisable(what, token, pkg)) {
191            if (SPEW) Slog.d(TAG, "Blocking disable request from " + pkg);
192            return;
193        }
194        disableInternal(mCurrentUserId, what, token, pkg);
195    }
196
197    private void disableInternal(int userId, int what, IBinder token, String pkg) {
198        enforceStatusBar();
199
200        synchronized (mLock) {
201            disableLocked(userId, what, token, pkg);
202        }
203    }
204
205    private void disableLocked(int userId, int what, IBinder token, String pkg) {
206        // It's important that the the callback and the call to mBar get done
207        // in the same order when multiple threads are calling this function
208        // so they are paired correctly.  The messages on the handler will be
209        // handled in the order they were enqueued, but will be outside the lock.
210        manageDisableListLocked(userId, what, token, pkg);
211
212        // Ensure state for the current user is applied, even if passed a non-current user.
213        final int net = gatherDisableActionsLocked(mCurrentUserId);
214        if (net != mDisabled) {
215            mDisabled = net;
216            mHandler.post(new Runnable() {
217                    public void run() {
218                        mNotificationDelegate.onSetDisabled(net);
219                    }
220                });
221            if (mBar != null) {
222                try {
223                    mBar.disable(net);
224                } catch (RemoteException ex) {
225                }
226            }
227        }
228    }
229
230    @Override
231    public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
232            String contentDescription) {
233        enforceStatusBar();
234
235        synchronized (mIcons) {
236            int index = mIcons.getSlotIndex(slot);
237            if (index < 0) {
238                throw new SecurityException("invalid status bar icon slot: " + slot);
239            }
240
241            StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId,
242                    iconLevel, 0,
243                    contentDescription);
244            //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
245            mIcons.setIcon(index, icon);
246
247            if (mBar != null) {
248                try {
249                    mBar.setIcon(index, icon);
250                } catch (RemoteException ex) {
251                }
252            }
253        }
254    }
255
256    @Override
257    public void setIconVisibility(String slot, boolean visible) {
258        enforceStatusBar();
259
260        synchronized (mIcons) {
261            int index = mIcons.getSlotIndex(slot);
262            if (index < 0) {
263                throw new SecurityException("invalid status bar icon slot: " + slot);
264            }
265
266            StatusBarIcon icon = mIcons.getIcon(index);
267            if (icon == null) {
268                return;
269            }
270
271            if (icon.visible != visible) {
272                icon.visible = visible;
273
274                if (mBar != null) {
275                    try {
276                        mBar.setIcon(index, icon);
277                    } catch (RemoteException ex) {
278                    }
279                }
280            }
281        }
282    }
283
284    @Override
285    public void removeIcon(String slot) {
286        enforceStatusBar();
287
288        synchronized (mIcons) {
289            int index = mIcons.getSlotIndex(slot);
290            if (index < 0) {
291                throw new SecurityException("invalid status bar icon slot: " + slot);
292            }
293
294            mIcons.removeIcon(index);
295
296            if (mBar != null) {
297                try {
298                    mBar.removeIcon(index);
299                } catch (RemoteException ex) {
300                }
301            }
302        }
303    }
304
305    /**
306     * Hide or show the on-screen Menu key. Only call this from the window manager, typically in
307     * response to a window with FLAG_NEEDS_MENU_KEY set.
308     */
309    @Override
310    public void topAppWindowChanged(final boolean menuVisible) {
311        enforceStatusBar();
312
313        if (SPEW) Slog.d(TAG, (menuVisible?"showing":"hiding") + " MENU key");
314
315        synchronized(mLock) {
316            mMenuVisible = menuVisible;
317            mHandler.post(new Runnable() {
318                    public void run() {
319                        if (mBar != null) {
320                            try {
321                                mBar.topAppWindowChanged(menuVisible);
322                            } catch (RemoteException ex) {
323                            }
324                        }
325                    }
326                });
327        }
328    }
329
330    @Override
331    public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition,
332            final boolean showImeSwitcher) {
333        enforceStatusBar();
334
335        if (SPEW) {
336            Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
337        }
338
339        synchronized(mLock) {
340            // In case of IME change, we need to call up setImeWindowStatus() regardless of
341            // mImeWindowVis because mImeWindowVis may not have been set to false when the
342            // previous IME was destroyed.
343            mImeWindowVis = vis;
344            mImeBackDisposition = backDisposition;
345            mImeToken = token;
346            mShowImeSwitcher = showImeSwitcher;
347            mHandler.post(new Runnable() {
348                public void run() {
349                    if (mBar != null) {
350                        try {
351                            mBar.setImeWindowStatus(token, vis, backDisposition, showImeSwitcher);
352                        } catch (RemoteException ex) {
353                        }
354                    }
355                }
356            });
357        }
358    }
359
360    @Override
361    public void setSystemUiVisibility(int vis, int mask) {
362        // also allows calls from window manager which is in this process.
363        enforceStatusBarService();
364
365        if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")");
366
367        synchronized (mLock) {
368            updateUiVisibilityLocked(vis, mask);
369            disableLocked(
370                    mCurrentUserId,
371                    vis & StatusBarManager.DISABLE_MASK,
372                    mSysUiVisToken,
373                    "WindowManager.LayoutParams");
374        }
375    }
376
377    private void updateUiVisibilityLocked(final int vis, final int mask) {
378        if (mSystemUiVisibility != vis) {
379            mSystemUiVisibility = vis;
380            mHandler.post(new Runnable() {
381                    public void run() {
382                        if (mBar != null) {
383                            try {
384                                mBar.setSystemUiVisibility(vis, mask);
385                            } catch (RemoteException ex) {
386                            }
387                        }
388                    }
389                });
390        }
391    }
392
393    @Override
394    public void toggleRecentApps() {
395        if (mBar != null) {
396            try {
397                mBar.toggleRecentApps();
398            } catch (RemoteException ex) {}
399        }
400    }
401
402    @Override
403    public void preloadRecentApps() {
404        if (mBar != null) {
405            try {
406                mBar.preloadRecentApps();
407            } catch (RemoteException ex) {}
408        }
409    }
410
411    @Override
412    public void cancelPreloadRecentApps() {
413        if (mBar != null) {
414            try {
415                mBar.cancelPreloadRecentApps();
416            } catch (RemoteException ex) {}
417        }
418    }
419
420    @Override
421    public void showRecentApps(boolean triggeredFromAltTab) {
422        if (mBar != null) {
423            try {
424                mBar.showRecentApps(triggeredFromAltTab);
425            } catch (RemoteException ex) {}
426        }
427    }
428
429    @Override
430    public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
431        if (mBar != null) {
432            try {
433                mBar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
434            } catch (RemoteException ex) {}
435        }
436    }
437
438    @Override
439    public void setCurrentUser(int newUserId) {
440        if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId);
441        mCurrentUserId = newUserId;
442    }
443
444    @Override
445    public void setWindowState(int window, int state) {
446        if (mBar != null) {
447            try {
448                mBar.setWindowState(window, state);
449            } catch (RemoteException ex) {}
450        }
451    }
452
453    private void enforceStatusBar() {
454        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR,
455                "StatusBarManagerService");
456    }
457
458    private void enforceExpandStatusBar() {
459        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR,
460                "StatusBarManagerService");
461    }
462
463    private void enforceStatusBarService() {
464        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
465                "StatusBarManagerService");
466    }
467
468    // ================================================================================
469    // Callbacks from the status bar service.
470    // ================================================================================
471    @Override
472    public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
473            int switches[], List<IBinder> binders) {
474        enforceStatusBarService();
475
476        Slog.i(TAG, "registerStatusBar bar=" + bar);
477        mBar = bar;
478        synchronized (mIcons) {
479            iconList.copyFrom(mIcons);
480        }
481        synchronized (mLock) {
482            switches[0] = gatherDisableActionsLocked(mCurrentUserId);
483            switches[1] = mSystemUiVisibility;
484            switches[2] = mMenuVisible ? 1 : 0;
485            switches[3] = mImeWindowVis;
486            switches[4] = mImeBackDisposition;
487            switches[5] = mShowImeSwitcher ? 1 : 0;
488            binders.add(mImeToken);
489        }
490    }
491
492    /**
493     * The status bar service should call this each time the user brings the panel from
494     * invisible to visible in order to clear the notification light.
495     */
496    @Override
497    public void onPanelRevealed() {
498        enforceStatusBarService();
499        long identity = Binder.clearCallingIdentity();
500        try {
501            // tell the notification manager to turn off the lights.
502            mNotificationDelegate.onPanelRevealed();
503        } finally {
504            Binder.restoreCallingIdentity(identity);
505        }
506    }
507
508    @Override
509    public void onPanelHidden() throws RemoteException {
510        enforceStatusBarService();
511        long identity = Binder.clearCallingIdentity();
512        try {
513            mNotificationDelegate.onPanelHidden();
514        } finally {
515            Binder.restoreCallingIdentity(identity);
516        }
517    }
518
519    @Override
520    public void onNotificationClick(String key) {
521        enforceStatusBarService();
522        final int callingUid = Binder.getCallingUid();
523        final int callingPid = Binder.getCallingPid();
524        long identity = Binder.clearCallingIdentity();
525        try {
526            mNotificationDelegate.onNotificationClick(callingUid, callingPid, key);
527        } finally {
528            Binder.restoreCallingIdentity(identity);
529        }
530    }
531
532    @Override
533    public void onNotificationError(String pkg, String tag, int id,
534            int uid, int initialPid, String message, int userId) {
535        enforceStatusBarService();
536        final int callingUid = Binder.getCallingUid();
537        final int callingPid = Binder.getCallingPid();
538        long identity = Binder.clearCallingIdentity();
539        try {
540            // WARNING: this will call back into us to do the remove.  Don't hold any locks.
541            mNotificationDelegate.onNotificationError(callingUid, callingPid,
542                    pkg, tag, id, uid, initialPid, message, userId);
543        } finally {
544            Binder.restoreCallingIdentity(identity);
545        }
546    }
547
548    @Override
549    public void onNotificationClear(String pkg, String tag, int id, int userId) {
550        enforceStatusBarService();
551        final int callingUid = Binder.getCallingUid();
552        final int callingPid = Binder.getCallingPid();
553        long identity = Binder.clearCallingIdentity();
554        try {
555            mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId);
556        } finally {
557            Binder.restoreCallingIdentity(identity);
558        }
559    }
560
561    @Override
562    public void onNotificationVisibilityChanged(
563            String[] newlyVisibleKeys, String[] noLongerVisibleKeys) throws RemoteException {
564        enforceStatusBarService();
565        long identity = Binder.clearCallingIdentity();
566        try {
567            mNotificationDelegate.onNotificationVisibilityChanged(
568                    newlyVisibleKeys, noLongerVisibleKeys);
569        } finally {
570            Binder.restoreCallingIdentity(identity);
571        }
572    }
573
574    @Override
575    public void onNotificationExpansionChanged(String key, boolean userAction,
576            boolean expanded) throws RemoteException {
577        enforceStatusBarService();
578        long identity = Binder.clearCallingIdentity();
579        try {
580            mNotificationDelegate.onNotificationExpansionChanged(
581                    key, userAction, expanded);
582        } finally {
583            Binder.restoreCallingIdentity(identity);
584        }
585    }
586
587    @Override
588    public void onClearAllNotifications(int userId) {
589        enforceStatusBarService();
590        final int callingUid = Binder.getCallingUid();
591        final int callingPid = Binder.getCallingPid();
592        long identity = Binder.clearCallingIdentity();
593        try {
594            mNotificationDelegate.onClearAll(callingUid, callingPid, userId);
595        } finally {
596            Binder.restoreCallingIdentity(identity);
597        }
598    }
599
600
601    // ================================================================================
602    // Can be called from any thread
603    // ================================================================================
604
605    // lock on mDisableRecords
606    void manageDisableListLocked(int userId, int what, IBinder token, String pkg) {
607        if (SPEW) {
608            Slog.d(TAG, "manageDisableList userId=" + userId
609                    + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
610        }
611        // update the list
612        final int N = mDisableRecords.size();
613        DisableRecord tok = null;
614        int i;
615        for (i=0; i<N; i++) {
616            DisableRecord t = mDisableRecords.get(i);
617            if (t.token == token && t.userId == userId) {
618                tok = t;
619                break;
620            }
621        }
622        if (what == 0 || !token.isBinderAlive()) {
623            if (tok != null) {
624                mDisableRecords.remove(i);
625                tok.token.unlinkToDeath(tok, 0);
626            }
627        } else {
628            if (tok == null) {
629                tok = new DisableRecord();
630                tok.userId = userId;
631                try {
632                    token.linkToDeath(tok, 0);
633                }
634                catch (RemoteException ex) {
635                    return; // give up
636                }
637                mDisableRecords.add(tok);
638            }
639            tok.what = what;
640            tok.token = token;
641            tok.pkg = pkg;
642        }
643    }
644
645    // lock on mDisableRecords
646    int gatherDisableActionsLocked(int userId) {
647        final int N = mDisableRecords.size();
648        // gather the new net flags
649        int net = 0;
650        for (int i=0; i<N; i++) {
651            final DisableRecord rec = mDisableRecords.get(i);
652            if (rec.userId == userId) {
653                net |= rec.what;
654            }
655        }
656        return net;
657    }
658
659    // ================================================================================
660    // Always called from UI thread
661    // ================================================================================
662
663    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
664        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
665                != PackageManager.PERMISSION_GRANTED) {
666            pw.println("Permission Denial: can't dump StatusBar from from pid="
667                    + Binder.getCallingPid()
668                    + ", uid=" + Binder.getCallingUid());
669            return;
670        }
671
672        synchronized (mIcons) {
673            mIcons.dump(pw);
674        }
675
676        synchronized (mLock) {
677            pw.println("  mDisabled=0x" + Integer.toHexString(mDisabled));
678            final int N = mDisableRecords.size();
679            pw.println("  mDisableRecords.size=" + N);
680            for (int i=0; i<N; i++) {
681                DisableRecord tok = mDisableRecords.get(i);
682                pw.println("    [" + i + "] userId=" + tok.userId
683                                + " what=0x" + Integer.toHexString(tok.what)
684                                + " pkg=" + tok.pkg
685                                + " token=" + tok.token);
686            }
687        }
688    }
689}
690