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