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