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