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.service.notification.StatusBarNotification; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.content.res.Resources; 26 import android.os.Binder; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.os.UserHandle; 31 import android.util.Slog; 32 33 import com.android.internal.statusbar.IStatusBar; 34 import com.android.internal.statusbar.IStatusBarService; 35 import com.android.internal.statusbar.StatusBarIcon; 36 import com.android.internal.statusbar.StatusBarIconList; 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 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