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