summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/statusbar/StatusBarManagerService.java')
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java701
1 files changed, 701 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
new file mode 100644
index 0000000..2ae467e
--- /dev/null
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -0,0 +1,701 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.statusbar;
+
+import android.app.StatusBarManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.Slog;
+
+import com.android.internal.statusbar.IStatusBar;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarIconList;
+import com.android.server.LocalServices;
+import com.android.server.notification.NotificationDelegate;
+import com.android.server.wm.WindowManagerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * A note on locking: We rely on the fact that calls onto mBar are oneway or
+ * if they are local, that they just enqueue messages to not deadlock.
+ */
+public class StatusBarManagerService extends IStatusBarService.Stub
+ implements WindowManagerService.OnHardKeyboardStatusChangeListener
+{
+ private static final String TAG = "StatusBarManagerService";
+ private static final boolean SPEW = false;
+
+ private final Context mContext;
+ private final WindowManagerService mWindowManager;
+ private Handler mHandler = new Handler();
+ private NotificationDelegate mNotificationDelegate;
+ private volatile IStatusBar mBar;
+ private StatusBarIconList mIcons = new StatusBarIconList();
+ private HashMap<IBinder,StatusBarNotification> mNotifications
+ = new HashMap<IBinder,StatusBarNotification>();
+
+ // for disabling the status bar
+ private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
+ private IBinder mSysUiVisToken = new Binder();
+ private int mDisabled = 0;
+
+ private Object mLock = new Object();
+ // encompasses lights-out mode and other flags defined on View
+ private int mSystemUiVisibility = 0;
+ private boolean mMenuVisible = false;
+ private int mImeWindowVis = 0;
+ private int mImeBackDisposition;
+ private IBinder mImeToken = null;
+ private int mCurrentUserId;
+
+ private class DisableRecord implements IBinder.DeathRecipient {
+ int userId;
+ String pkg;
+ int what;
+ IBinder token;
+
+ public void binderDied() {
+ Slog.i(TAG, "binder died for pkg=" + pkg);
+ disableInternal(userId, 0, token, pkg);
+ token.unlinkToDeath(this, 0);
+ }
+ }
+
+ /**
+ * Construct the service, add the status bar view to the window manager
+ */
+ public StatusBarManagerService(Context context, WindowManagerService windowManager) {
+ mContext = context;
+ mWindowManager = windowManager;
+ mWindowManager.setOnHardKeyboardStatusChangeListener(this);
+
+ final Resources res = context.getResources();
+ mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));
+
+ LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
+ }
+
+ /**
+ * Private API used by NotificationManagerService.
+ */
+ private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
+ @Override
+ public void setNotificationDelegate(NotificationDelegate delegate) {
+ synchronized (mNotifications) {
+ mNotificationDelegate = delegate;
+ }
+ }
+
+ @Override
+ public IBinder addNotification(StatusBarNotification notification) {
+ synchronized (mNotifications) {
+ IBinder key = new Binder();
+ mNotifications.put(key, notification);
+ if (mBar != null) {
+ try {
+ mBar.addNotification(key, notification);
+ } catch (RemoteException ex) {
+ }
+ }
+ return key;
+ }
+ }
+
+ @Override
+ public void updateNotification(IBinder key, StatusBarNotification notification) {
+ synchronized (mNotifications) {
+ if (!mNotifications.containsKey(key)) {
+ throw new IllegalArgumentException("updateNotification key not found: " + key);
+ }
+ mNotifications.put(key, notification);
+ if (mBar != null) {
+ try {
+ mBar.updateNotification(key, notification);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeNotification(IBinder key) {
+ synchronized (mNotifications) {
+ final StatusBarNotification n = mNotifications.remove(key);
+ if (n == null) {
+ Slog.e(TAG, "removeNotification key not found: " + key);
+ return;
+ }
+ if (mBar != null) {
+ try {
+ mBar.removeNotification(key);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+ };
+
+ // ================================================================================
+ // From IStatusBarService
+ // ================================================================================
+ @Override
+ public void expandNotificationsPanel() {
+ enforceExpandStatusBar();
+
+ if (mBar != null) {
+ try {
+ mBar.animateExpandNotificationsPanel();
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
+ public void collapsePanels() {
+ enforceExpandStatusBar();
+
+ if (mBar != null) {
+ try {
+ mBar.animateCollapsePanels();
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
+ public void expandSettingsPanel() {
+ enforceExpandStatusBar();
+
+ if (mBar != null) {
+ try {
+ mBar.animateExpandSettingsPanel();
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
+ public void disable(int what, IBinder token, String pkg) {
+ disableInternal(mCurrentUserId, what, token, pkg);
+ }
+
+ private void disableInternal(int userId, int what, IBinder token, String pkg) {
+ enforceStatusBar();
+
+ synchronized (mLock) {
+ disableLocked(userId, what, token, pkg);
+ }
+ }
+
+ private void disableLocked(int userId, int what, IBinder token, String pkg) {
+ // It's important that the the callback and the call to mBar get done
+ // in the same order when multiple threads are calling this function
+ // so they are paired correctly. The messages on the handler will be
+ // handled in the order they were enqueued, but will be outside the lock.
+ manageDisableListLocked(userId, what, token, pkg);
+
+ // Ensure state for the current user is applied, even if passed a non-current user.
+ final int net = gatherDisableActionsLocked(mCurrentUserId);
+ if (net != mDisabled) {
+ mDisabled = net;
+ mHandler.post(new Runnable() {
+ public void run() {
+ mNotificationDelegate.onSetDisabled(net);
+ }
+ });
+ if (mBar != null) {
+ try {
+ mBar.disable(net);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
+ String contentDescription) {
+ enforceStatusBar();
+
+ synchronized (mIcons) {
+ int index = mIcons.getSlotIndex(slot);
+ if (index < 0) {
+ throw new SecurityException("invalid status bar icon slot: " + slot);
+ }
+
+ StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId,
+ iconLevel, 0,
+ contentDescription);
+ //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
+ mIcons.setIcon(index, icon);
+
+ if (mBar != null) {
+ try {
+ mBar.setIcon(index, icon);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setIconVisibility(String slot, boolean visible) {
+ enforceStatusBar();
+
+ synchronized (mIcons) {
+ int index = mIcons.getSlotIndex(slot);
+ if (index < 0) {
+ throw new SecurityException("invalid status bar icon slot: " + slot);
+ }
+
+ StatusBarIcon icon = mIcons.getIcon(index);
+ if (icon == null) {
+ return;
+ }
+
+ if (icon.visible != visible) {
+ icon.visible = visible;
+
+ if (mBar != null) {
+ try {
+ mBar.setIcon(index, icon);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeIcon(String slot) {
+ enforceStatusBar();
+
+ synchronized (mIcons) {
+ int index = mIcons.getSlotIndex(slot);
+ if (index < 0) {
+ throw new SecurityException("invalid status bar icon slot: " + slot);
+ }
+
+ mIcons.removeIcon(index);
+
+ if (mBar != null) {
+ try {
+ mBar.removeIcon(index);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Hide or show the on-screen Menu key. Only call this from the window manager, typically in
+ * response to a window with FLAG_NEEDS_MENU_KEY set.
+ */
+ @Override
+ public void topAppWindowChanged(final boolean menuVisible) {
+ enforceStatusBar();
+
+ if (SPEW) Slog.d(TAG, (menuVisible?"showing":"hiding") + " MENU key");
+
+ synchronized(mLock) {
+ mMenuVisible = menuVisible;
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mBar != null) {
+ try {
+ mBar.topAppWindowChanged(menuVisible);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) {
+ enforceStatusBar();
+
+ if (SPEW) {
+ Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
+ }
+
+ synchronized(mLock) {
+ // In case of IME change, we need to call up setImeWindowStatus() regardless of
+ // mImeWindowVis because mImeWindowVis may not have been set to false when the
+ // previous IME was destroyed.
+ mImeWindowVis = vis;
+ mImeBackDisposition = backDisposition;
+ mImeToken = token;
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mBar != null) {
+ try {
+ mBar.setImeWindowStatus(token, vis, backDisposition);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setSystemUiVisibility(int vis, int mask) {
+ // also allows calls from window manager which is in this process.
+ enforceStatusBarService();
+
+ if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")");
+
+ synchronized (mLock) {
+ updateUiVisibilityLocked(vis, mask);
+ disableLocked(
+ mCurrentUserId,
+ vis & StatusBarManager.DISABLE_MASK,
+ mSysUiVisToken,
+ "WindowManager.LayoutParams");
+ }
+ }
+
+ private void updateUiVisibilityLocked(final int vis, final int mask) {
+ if (mSystemUiVisibility != vis) {
+ mSystemUiVisibility = vis;
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mBar != null) {
+ try {
+ mBar.setSystemUiVisibility(vis, mask);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setHardKeyboardEnabled(final boolean enabled) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mWindowManager.setHardKeyboardEnabled(enabled);
+ }
+ });
+ }
+
+ @Override
+ public void onHardKeyboardStatusChange(final boolean available, final boolean enabled) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mBar != null) {
+ try {
+ mBar.setHardKeyboardStatus(available, enabled);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void toggleRecentApps() {
+ if (mBar != null) {
+ try {
+ mBar.toggleRecentApps();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
+ public void preloadRecentApps() {
+ if (mBar != null) {
+ try {
+ mBar.preloadRecentApps();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
+ public void cancelPreloadRecentApps() {
+ if (mBar != null) {
+ try {
+ mBar.cancelPreloadRecentApps();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
+ public void setCurrentUser(int newUserId) {
+ if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId);
+ mCurrentUserId = newUserId;
+ }
+
+ @Override
+ public void setWindowState(int window, int state) {
+ if (mBar != null) {
+ try {
+ mBar.setWindowState(window, state);
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ private void enforceStatusBar() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR,
+ "StatusBarManagerService");
+ }
+
+ private void enforceExpandStatusBar() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR,
+ "StatusBarManagerService");
+ }
+
+ private void enforceStatusBarService() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ "StatusBarManagerService");
+ }
+
+ // ================================================================================
+ // Callbacks from the status bar service.
+ // ================================================================================
+ @Override
+ public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
+ List<IBinder> notificationKeys, List<StatusBarNotification> notifications,
+ int switches[], List<IBinder> binders) {
+ enforceStatusBarService();
+
+ Slog.i(TAG, "registerStatusBar bar=" + bar);
+ mBar = bar;
+ synchronized (mIcons) {
+ iconList.copyFrom(mIcons);
+ }
+ synchronized (mNotifications) {
+ for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {
+ notificationKeys.add(e.getKey());
+ notifications.add(e.getValue());
+ }
+ }
+ synchronized (mLock) {
+ switches[0] = gatherDisableActionsLocked(mCurrentUserId);
+ switches[1] = mSystemUiVisibility;
+ switches[2] = mMenuVisible ? 1 : 0;
+ switches[3] = mImeWindowVis;
+ switches[4] = mImeBackDisposition;
+ binders.add(mImeToken);
+ }
+ switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0;
+ switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0;
+ }
+
+ /**
+ * The status bar service should call this each time the user brings the panel from
+ * invisible to visible in order to clear the notification light.
+ */
+ @Override
+ public void onPanelRevealed() {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ // tell the notification manager to turn off the lights.
+ mNotificationDelegate.onPanelRevealed();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onNotificationClick(String pkg, String tag, int id) {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationClick(pkg, tag, id);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onNotificationError(String pkg, String tag, int id,
+ int uid, int initialPid, String message) {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ // WARNING: this will call back into us to do the remove. Don't hold any locks.
+ mNotificationDelegate.onNotificationError(pkg, tag, id, uid, initialPid, message);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onNotificationClear(String pkg, String tag, int id) {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationClear(pkg, tag, id);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onClearAllNotifications() {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onClearAll();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+
+ // ================================================================================
+ // Can be called from any thread
+ // ================================================================================
+
+ // lock on mDisableRecords
+ void manageDisableListLocked(int userId, int what, IBinder token, String pkg) {
+ if (SPEW) {
+ Slog.d(TAG, "manageDisableList userId=" + userId
+ + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
+ }
+ // update the list
+ final int N = mDisableRecords.size();
+ DisableRecord tok = null;
+ int i;
+ for (i=0; i<N; i++) {
+ DisableRecord t = mDisableRecords.get(i);
+ if (t.token == token && t.userId == userId) {
+ tok = t;
+ break;
+ }
+ }
+ if (what == 0 || !token.isBinderAlive()) {
+ if (tok != null) {
+ mDisableRecords.remove(i);
+ tok.token.unlinkToDeath(tok, 0);
+ }
+ } else {
+ if (tok == null) {
+ tok = new DisableRecord();
+ tok.userId = userId;
+ try {
+ token.linkToDeath(tok, 0);
+ }
+ catch (RemoteException ex) {
+ return; // give up
+ }
+ mDisableRecords.add(tok);
+ }
+ tok.what = what;
+ tok.token = token;
+ tok.pkg = pkg;
+ }
+ }
+
+ // lock on mDisableRecords
+ int gatherDisableActionsLocked(int userId) {
+ final int N = mDisableRecords.size();
+ // gather the new net flags
+ int net = 0;
+ for (int i=0; i<N; i++) {
+ final DisableRecord rec = mDisableRecords.get(i);
+ if (rec.userId == userId) {
+ net |= rec.what;
+ }
+ }
+ return net;
+ }
+
+ // ================================================================================
+ // Always called from UI thread
+ // ================================================================================
+
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump StatusBar from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (mIcons) {
+ mIcons.dump(pw);
+ }
+
+ synchronized (mNotifications) {
+ int i=0;
+ pw.println("Notification list:");
+ for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {
+ pw.printf(" %2d: %s\n", i, e.getValue().toString());
+ i++;
+ }
+ }
+
+ synchronized (mLock) {
+ pw.println(" mDisabled=0x" + Integer.toHexString(mDisabled));
+ final int N = mDisableRecords.size();
+ pw.println(" mDisableRecords.size=" + N);
+ for (int i=0; i<N; i++) {
+ DisableRecord tok = mDisableRecords.get(i);
+ pw.println(" [" + i + "] userId=" + tok.userId
+ + " what=0x" + Integer.toHexString(tok.what)
+ + " pkg=" + tok.pkg
+ + " token=" + tok.token);
+ }
+ }
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_SCREEN_OFF.equals(action)) {
+ collapsePanels();
+ }
+ /*
+ else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
+ updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false),
+ intent.getStringExtra(Telephony.Intents.EXTRA_SPN),
+ intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false),
+ intent.getStringExtra(Telephony.Intents.EXTRA_PLMN));
+ }
+ else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+ updateResources();
+ }
+ */
+ }
+ };
+
+}