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