StatusBarManagerService.java revision 9305647eb61bb60a1f42481a0c0d208dc9bbe965
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.PendingIntent;
20import android.app.StatusBarManager;
21import android.content.BroadcastReceiver;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.pm.PackageManager;
27import android.content.res.Resources;
28import android.net.Uri;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.os.Binder;
32import android.os.Handler;
33import android.os.SystemClock;
34import android.util.Slog;
35
36import com.android.internal.statusbar.IStatusBar;
37import com.android.internal.statusbar.IStatusBarService;
38import com.android.internal.statusbar.StatusBarIcon;
39import com.android.internal.statusbar.StatusBarIconList;
40import com.android.internal.statusbar.StatusBarNotification;
41
42import java.io.FileDescriptor;
43import java.io.PrintWriter;
44import java.util.ArrayList;
45import java.util.HashMap;
46import java.util.List;
47import java.util.Map;
48
49
50/**
51 * A note on locking:  We rely on the fact that calls onto mBar are oneway or
52 * if they are local, that they just enqueue messages to not deadlock.
53 */
54public class StatusBarManagerService extends IStatusBarService.Stub
55{
56    static final String TAG = "StatusBarManagerService";
57    static final boolean SPEW = true;
58
59    final Context mContext;
60    Handler mHandler = new Handler();
61    NotificationCallbacks mNotificationCallbacks;
62    volatile IStatusBar mBar;
63    StatusBarIconList mIcons = new StatusBarIconList();
64    HashMap<IBinder,StatusBarNotification> mNotifications
65            = new HashMap<IBinder,StatusBarNotification>();
66
67    // for disabling the status bar
68    ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
69    int mDisabled = 0;
70
71    Object mLock = new Object();
72    // We usually call it lights out mode, but double negatives are annoying
73    boolean mLightsOn = true;
74
75    private class DisableRecord implements IBinder.DeathRecipient {
76        String pkg;
77        int what;
78        IBinder token;
79
80        public void binderDied() {
81            Slog.i(TAG, "binder died for pkg=" + pkg);
82            disable(0, token, pkg);
83            token.unlinkToDeath(this, 0);
84        }
85    }
86
87    public interface NotificationCallbacks {
88        void onSetDisabled(int status);
89        void onClearAll();
90        void onNotificationClick(String pkg, String tag, int id);
91        void onNotificationClear(String pkg, String tag, int id);
92        void onPanelRevealed();
93        void onNotificationError(String pkg, String tag, int id,
94                int uid, int initialPid, String message);
95    }
96
97    /**
98     * Construct the service, add the status bar view to the window manager
99     */
100    public StatusBarManagerService(Context context) {
101        mContext = context;
102
103        final Resources res = context.getResources();
104        mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));
105    }
106
107    public void setNotificationCallbacks(NotificationCallbacks listener) {
108        mNotificationCallbacks = listener;
109    }
110
111    // ================================================================================
112    // Constructing the view
113    // ================================================================================
114
115    public void systemReady() {
116    }
117
118    public void systemReady2() {
119        ComponentName cn = ComponentName.unflattenFromString(
120                mContext.getString(com.android.internal.R.string.config_statusBarComponent));
121        Intent intent = new Intent();
122        intent.setComponent(cn);
123        Slog.i(TAG, "Starting service: " + cn);
124        mContext.startService(intent);
125    }
126
127    // ================================================================================
128    // From IStatusBarService
129    // ================================================================================
130    public void expand() {
131        enforceExpandStatusBar();
132
133        if (mBar != null) {
134            try {
135                mBar.animateExpand();
136            } catch (RemoteException ex) {
137            }
138        }
139    }
140
141    public void collapse() {
142        enforceExpandStatusBar();
143
144        if (mBar != null) {
145            try {
146                mBar.animateCollapse();
147            } catch (RemoteException ex) {
148            }
149        }
150    }
151
152    public void disable(int what, IBinder token, String pkg) {
153        enforceStatusBar();
154
155        // It's important that the the callback and the call to mBar get done
156        // in the same order when multiple threads are calling this function
157        // so they are paired correctly.  The messages on the handler will be
158        // handled in the order they were enqueued, but will be outside the lock.
159        synchronized (mDisableRecords) {
160            manageDisableListLocked(what, token, pkg);
161            final int net = gatherDisableActionsLocked();
162            Slog.d(TAG, "disable... net=0x" + Integer.toHexString(net));
163            if (net != mDisabled) {
164                mDisabled = net;
165                mHandler.post(new Runnable() {
166                        public void run() {
167                            mNotificationCallbacks.onSetDisabled(net);
168                        }
169                    });
170                if (mBar != null) {
171                    try {
172                        mBar.disable(net);
173                    } catch (RemoteException ex) {
174                    }
175                }
176            }
177        }
178    }
179
180    public void setIcon(String slot, String iconPackage, int iconId, int iconLevel) {
181        enforceStatusBar();
182
183        synchronized (mIcons) {
184            int index = mIcons.getSlotIndex(slot);
185            if (index < 0) {
186                throw new SecurityException("invalid status bar icon slot: " + slot);
187            }
188
189            StatusBarIcon icon = new StatusBarIcon(iconPackage, iconId, iconLevel);
190            //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
191            mIcons.setIcon(index, icon);
192
193            if (mBar != null) {
194                try {
195                    mBar.setIcon(index, icon);
196                } catch (RemoteException ex) {
197                }
198            }
199        }
200    }
201
202    public void setIconVisibility(String slot, boolean visible) {
203        enforceStatusBar();
204
205        synchronized (mIcons) {
206            int index = mIcons.getSlotIndex(slot);
207            if (index < 0) {
208                throw new SecurityException("invalid status bar icon slot: " + slot);
209            }
210
211            StatusBarIcon icon = mIcons.getIcon(index);
212            if (icon == null) {
213                return;
214            }
215
216            if (icon.visible != visible) {
217                icon.visible = visible;
218
219                if (mBar != null) {
220                    try {
221                        mBar.setIcon(index, icon);
222                    } catch (RemoteException ex) {
223                    }
224                }
225            }
226        }
227    }
228
229    public void removeIcon(String slot) {
230        enforceStatusBar();
231
232        synchronized (mIcons) {
233            int index = mIcons.getSlotIndex(slot);
234            if (index < 0) {
235                throw new SecurityException("invalid status bar icon slot: " + slot);
236            }
237
238            mIcons.removeIcon(index);
239
240            if (mBar != null) {
241                try {
242                    mBar.removeIcon(index);
243                } catch (RemoteException ex) {
244                }
245            }
246        }
247    }
248
249    public void setActiveWindowIsFullscreen(boolean fullscreen) {
250        // We could get away with a separate permission here, but STATUS_BAR is
251        // signatureOrSystem which is probably good enough.  There is no public API
252        // for this, so the question is a security issue, not an API compatibility issue.
253        enforceStatusBar();
254
255        final boolean lightsOn = !fullscreen;
256        synchronized (mLock) {
257            if (mLightsOn != lightsOn) {
258                mLightsOn = lightsOn;
259                mHandler.post(new Runnable() {
260                        public void run() {
261                            if (mBar != null) {
262                                try {
263                                    mBar.setLightsOn(lightsOn);
264                                } catch (RemoteException ex) {
265                                }
266                            }
267                        }
268                    });
269            }
270        }
271    }
272
273    private void enforceStatusBar() {
274        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR,
275                "StatusBarManagerService");
276    }
277
278    private void enforceExpandStatusBar() {
279        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR,
280                "StatusBarManagerService");
281    }
282
283    private void enforceStatusBarService() {
284        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
285                "StatusBarManagerService");
286    }
287
288
289    // ================================================================================
290    // Callbacks from the status bar service.
291    // ================================================================================
292    public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
293            List<IBinder> notificationKeys, List<StatusBarNotification> notifications,
294            boolean lightsOn[]) {
295        enforceStatusBarService();
296
297        Slog.i(TAG, "registerStatusBar bar=" + bar);
298        mBar = bar;
299        synchronized (mIcons) {
300            iconList.copyFrom(mIcons);
301        }
302        synchronized (mNotifications) {
303            for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {
304                notificationKeys.add(e.getKey());
305                notifications.add(e.getValue());
306            }
307        }
308        synchronized (mLock) {
309            lightsOn[0] = mLightsOn;
310        }
311    }
312
313    /**
314     * The status bar service should call this each time the user brings the panel from
315     * invisible to visible in order to clear the notification light.
316     */
317    public void onPanelRevealed() {
318        enforceStatusBarService();
319
320        // tell the notification manager to turn off the lights.
321        mNotificationCallbacks.onPanelRevealed();
322    }
323
324    public void onNotificationClick(String pkg, String tag, int id) {
325        enforceStatusBarService();
326
327        mNotificationCallbacks.onNotificationClick(pkg, tag, id);
328    }
329
330    public void onNotificationError(String pkg, String tag, int id,
331            int uid, int initialPid, String message) {
332        enforceStatusBarService();
333
334        // WARNING: this will call back into us to do the remove.  Don't hold any locks.
335        mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message);
336    }
337
338    public void onNotificationClear(String pkg, String tag, int id) {
339        enforceStatusBarService();
340
341        mNotificationCallbacks.onNotificationClear(pkg, tag, id);
342    }
343
344    public void onClearAllNotifications() {
345        enforceStatusBarService();
346
347        mNotificationCallbacks.onClearAll();
348    }
349
350    // ================================================================================
351    // Callbacks for NotificationManagerService.
352    // ================================================================================
353    public IBinder addNotification(StatusBarNotification notification) {
354        synchronized (mNotifications) {
355            IBinder key = new Binder();
356            mNotifications.put(key, notification);
357            if (mBar != null) {
358                try {
359                    mBar.addNotification(key, notification);
360                } catch (RemoteException ex) {
361                }
362            }
363            return key;
364        }
365    }
366
367    public void updateNotification(IBinder key, StatusBarNotification notification) {
368        synchronized (mNotifications) {
369            if (!mNotifications.containsKey(key)) {
370                throw new IllegalArgumentException("updateNotification key not found: " + key);
371            }
372            mNotifications.put(key, notification);
373            if (mBar != null) {
374                try {
375                    mBar.updateNotification(key, notification);
376                } catch (RemoteException ex) {
377                }
378            }
379        }
380    }
381
382    public void removeNotification(IBinder key) {
383        synchronized (mNotifications) {
384            final StatusBarNotification n = mNotifications.remove(key);
385            if (n == null) {
386                throw new IllegalArgumentException("removeNotification key not found: " + key);
387            }
388            if (mBar != null) {
389                try {
390                    mBar.removeNotification(key);
391                } catch (RemoteException ex) {
392                }
393            }
394        }
395    }
396
397    // ================================================================================
398    // Can be called from any thread
399    // ================================================================================
400
401    // lock on mDisableRecords
402    void manageDisableListLocked(int what, IBinder token, String pkg) {
403        if (SPEW) {
404            Slog.d(TAG, "manageDisableList what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
405        }
406        // update the list
407        synchronized (mDisableRecords) {
408            final int N = mDisableRecords.size();
409            DisableRecord tok = null;
410            int i;
411            for (i=0; i<N; i++) {
412                DisableRecord t = mDisableRecords.get(i);
413                if (t.token == token) {
414                    tok = t;
415                    break;
416                }
417            }
418            if (what == 0 || !token.isBinderAlive()) {
419                if (tok != null) {
420                    mDisableRecords.remove(i);
421                    tok.token.unlinkToDeath(tok, 0);
422                }
423            } else {
424                if (tok == null) {
425                    tok = new DisableRecord();
426                    try {
427                        token.linkToDeath(tok, 0);
428                    }
429                    catch (RemoteException ex) {
430                        return; // give up
431                    }
432                    mDisableRecords.add(tok);
433                }
434                tok.what = what;
435                tok.token = token;
436                tok.pkg = pkg;
437            }
438        }
439    }
440
441    // lock on mDisableRecords
442    int gatherDisableActionsLocked() {
443        final int N = mDisableRecords.size();
444        // gather the new net flags
445        int net = 0;
446        for (int i=0; i<N; i++) {
447            net |= mDisableRecords.get(i).what;
448        }
449        return net;
450    }
451
452    // ================================================================================
453    // Always called from UI thread
454    // ================================================================================
455
456    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
457        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
458                != PackageManager.PERMISSION_GRANTED) {
459            pw.println("Permission Denial: can't dump StatusBar from from pid="
460                    + Binder.getCallingPid()
461                    + ", uid=" + Binder.getCallingUid());
462            return;
463        }
464
465        synchronized (mIcons) {
466            mIcons.dump(pw);
467        }
468
469        synchronized (mNotifications) {
470            int i=0;
471            pw.println("Notification list:");
472            for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {
473                pw.printf("  %2d: %s\n", i, e.getValue().toString());
474                i++;
475            }
476        }
477
478        synchronized (mDisableRecords) {
479            final int N = mDisableRecords.size();
480            pw.println("  mDisableRecords.size=" + N
481                    + " mDisabled=0x" + Integer.toHexString(mDisabled));
482            for (int i=0; i<N; i++) {
483                DisableRecord tok = mDisableRecords.get(i);
484                pw.println("    [" + i + "] what=0x" + Integer.toHexString(tok.what)
485                                + " pkg=" + tok.pkg + " token=" + tok.token);
486            }
487        }
488    }
489
490    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
491        public void onReceive(Context context, Intent intent) {
492            String action = intent.getAction();
493            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
494                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
495                collapse();
496            }
497            /*
498            else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
499                updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false),
500                        intent.getStringExtra(Telephony.Intents.EXTRA_SPN),
501                        intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false),
502                        intent.getStringExtra(Telephony.Intents.EXTRA_PLMN));
503            }
504            else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
505                updateResources();
506            }
507            */
508        }
509    };
510
511}
512