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