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