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