StatusBarManagerService.java revision 1e8d71b605b4872e93200706a80a88a3ff25498c
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 showRecentApps(boolean triggeredFromAltTab) {
464        if (mBar != null) {
465            try {
466                mBar.showRecentApps(triggeredFromAltTab);
467            } catch (RemoteException ex) {}
468        }
469    }
470
471    @Override
472    public void hideRecentApps() {
473        if (mBar != null) {
474            try {
475                mBar.hideRecentApps();
476            } catch (RemoteException ex) {}
477        }
478    }
479
480    @Override
481    public void setCurrentUser(int newUserId) {
482        if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId);
483        mCurrentUserId = newUserId;
484    }
485
486    @Override
487    public void setWindowState(int window, int state) {
488        if (mBar != null) {
489            try {
490                mBar.setWindowState(window, state);
491            } catch (RemoteException ex) {}
492        }
493    }
494
495    private void enforceStatusBar() {
496        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR,
497                "StatusBarManagerService");
498    }
499
500    private void enforceExpandStatusBar() {
501        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR,
502                "StatusBarManagerService");
503    }
504
505    private void enforceStatusBarService() {
506        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
507                "StatusBarManagerService");
508    }
509
510    // ================================================================================
511    // Callbacks from the status bar service.
512    // ================================================================================
513    @Override
514    public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
515            List<IBinder> notificationKeys, List<StatusBarNotification> notifications,
516            int switches[], List<IBinder> binders) {
517        enforceStatusBarService();
518
519        Slog.i(TAG, "registerStatusBar bar=" + bar);
520        mBar = bar;
521        synchronized (mIcons) {
522            iconList.copyFrom(mIcons);
523        }
524        synchronized (mNotifications) {
525            for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {
526                notificationKeys.add(e.getKey());
527                notifications.add(e.getValue());
528            }
529        }
530        synchronized (mLock) {
531            switches[0] = gatherDisableActionsLocked(mCurrentUserId);
532            switches[1] = mSystemUiVisibility;
533            switches[2] = mMenuVisible ? 1 : 0;
534            switches[3] = mImeWindowVis;
535            switches[4] = mImeBackDisposition;
536            switches[7] = mShowImeSwitcher ? 1 : 0;
537            binders.add(mImeToken);
538        }
539        switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0;
540        switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0;
541    }
542
543    /**
544     * The status bar service should call this each time the user brings the panel from
545     * invisible to visible in order to clear the notification light.
546     */
547    @Override
548    public void onPanelRevealed() {
549        enforceStatusBarService();
550        long identity = Binder.clearCallingIdentity();
551        try {
552            // tell the notification manager to turn off the lights.
553            mNotificationDelegate.onPanelRevealed();
554        } finally {
555            Binder.restoreCallingIdentity(identity);
556        }
557    }
558
559    @Override
560    public void onPanelHidden() throws RemoteException {
561        enforceStatusBarService();
562        long identity = Binder.clearCallingIdentity();
563        try {
564            mNotificationDelegate.onPanelHidden();
565        } finally {
566            Binder.restoreCallingIdentity(identity);
567        }
568    }
569
570    @Override
571    public void onNotificationClick(String key) {
572        enforceStatusBarService();
573        final int callingUid = Binder.getCallingUid();
574        final int callingPid = Binder.getCallingPid();
575        long identity = Binder.clearCallingIdentity();
576        try {
577            mNotificationDelegate.onNotificationClick(callingUid, callingPid, key);
578        } finally {
579            Binder.restoreCallingIdentity(identity);
580        }
581    }
582
583    @Override
584    public void onNotificationError(String pkg, String tag, int id,
585            int uid, int initialPid, String message, int userId) {
586        enforceStatusBarService();
587        final int callingUid = Binder.getCallingUid();
588        final int callingPid = Binder.getCallingPid();
589        long identity = Binder.clearCallingIdentity();
590        try {
591            // WARNING: this will call back into us to do the remove.  Don't hold any locks.
592            mNotificationDelegate.onNotificationError(callingUid, callingPid,
593                    pkg, tag, id, uid, initialPid, message, userId);
594        } finally {
595            Binder.restoreCallingIdentity(identity);
596        }
597    }
598
599    @Override
600    public void onNotificationClear(String pkg, String tag, int id, int userId) {
601        enforceStatusBarService();
602        final int callingUid = Binder.getCallingUid();
603        final int callingPid = Binder.getCallingPid();
604        long identity = Binder.clearCallingIdentity();
605        try {
606            mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId);
607        } finally {
608            Binder.restoreCallingIdentity(identity);
609        }
610    }
611
612    @Override
613    public void onNotificationVisibilityChanged(
614            String[] newlyVisibleKeys, String[] noLongerVisibleKeys) throws RemoteException {
615        enforceStatusBarService();
616        long identity = Binder.clearCallingIdentity();
617        try {
618            mNotificationDelegate.onNotificationVisibilityChanged(
619                    newlyVisibleKeys, noLongerVisibleKeys);
620        } finally {
621            Binder.restoreCallingIdentity(identity);
622        }
623    }
624
625    @Override
626    public void onClearAllNotifications(int userId) {
627        enforceStatusBarService();
628        final int callingUid = Binder.getCallingUid();
629        final int callingPid = Binder.getCallingPid();
630        long identity = Binder.clearCallingIdentity();
631        try {
632            mNotificationDelegate.onClearAll(callingUid, callingPid, userId);
633        } finally {
634            Binder.restoreCallingIdentity(identity);
635        }
636    }
637
638
639    // ================================================================================
640    // Can be called from any thread
641    // ================================================================================
642
643    // lock on mDisableRecords
644    void manageDisableListLocked(int userId, int what, IBinder token, String pkg) {
645        if (SPEW) {
646            Slog.d(TAG, "manageDisableList userId=" + userId
647                    + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
648        }
649        // update the list
650        final int N = mDisableRecords.size();
651        DisableRecord tok = null;
652        int i;
653        for (i=0; i<N; i++) {
654            DisableRecord t = mDisableRecords.get(i);
655            if (t.token == token && t.userId == userId) {
656                tok = t;
657                break;
658            }
659        }
660        if (what == 0 || !token.isBinderAlive()) {
661            if (tok != null) {
662                mDisableRecords.remove(i);
663                tok.token.unlinkToDeath(tok, 0);
664            }
665        } else {
666            if (tok == null) {
667                tok = new DisableRecord();
668                tok.userId = userId;
669                try {
670                    token.linkToDeath(tok, 0);
671                }
672                catch (RemoteException ex) {
673                    return; // give up
674                }
675                mDisableRecords.add(tok);
676            }
677            tok.what = what;
678            tok.token = token;
679            tok.pkg = pkg;
680        }
681    }
682
683    // lock on mDisableRecords
684    int gatherDisableActionsLocked(int userId) {
685        final int N = mDisableRecords.size();
686        // gather the new net flags
687        int net = 0;
688        for (int i=0; i<N; i++) {
689            final DisableRecord rec = mDisableRecords.get(i);
690            if (rec.userId == userId) {
691                net |= rec.what;
692            }
693        }
694        return net;
695    }
696
697    // ================================================================================
698    // Always called from UI thread
699    // ================================================================================
700
701    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
702        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
703                != PackageManager.PERMISSION_GRANTED) {
704            pw.println("Permission Denial: can't dump StatusBar from from pid="
705                    + Binder.getCallingPid()
706                    + ", uid=" + Binder.getCallingUid());
707            return;
708        }
709
710        synchronized (mIcons) {
711            mIcons.dump(pw);
712        }
713
714        synchronized (mNotifications) {
715            int i=0;
716            pw.println("Notification list:");
717            for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {
718                pw.printf("  %2d: %s\n", i, e.getValue().toString());
719                i++;
720            }
721        }
722
723        synchronized (mLock) {
724            pw.println("  mDisabled=0x" + Integer.toHexString(mDisabled));
725            final int N = mDisableRecords.size();
726            pw.println("  mDisableRecords.size=" + N);
727            for (int i=0; i<N; i++) {
728                DisableRecord tok = mDisableRecords.get(i);
729                pw.println("    [" + i + "] userId=" + tok.userId
730                                + " what=0x" + Integer.toHexString(tok.what)
731                                + " pkg=" + tok.pkg
732                                + " token=" + tok.token);
733            }
734        }
735    }
736}
737