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