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