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