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