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