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 private void enforceStatusBar() { 403 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, 404 "StatusBarManagerService"); 405 } 406 407 private void enforceExpandStatusBar() { 408 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR, 409 "StatusBarManagerService"); 410 } 411 412 private void enforceStatusBarService() { 413 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, 414 "StatusBarManagerService"); 415 } 416 417 // ================================================================================ 418 // Callbacks from the status bar service. 419 // ================================================================================ 420 public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, 421 List<IBinder> notificationKeys, List<StatusBarNotification> notifications, 422 int switches[], List<IBinder> binders) { 423 enforceStatusBarService(); 424 425 Slog.i(TAG, "registerStatusBar bar=" + bar); 426 mBar = bar; 427 synchronized (mIcons) { 428 iconList.copyFrom(mIcons); 429 } 430 synchronized (mNotifications) { 431 for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) { 432 notificationKeys.add(e.getKey()); 433 notifications.add(e.getValue()); 434 } 435 } 436 synchronized (mLock) { 437 switches[0] = gatherDisableActionsLocked(mCurrentUserId); 438 switches[1] = mSystemUiVisibility; 439 switches[2] = mMenuVisible ? 1 : 0; 440 switches[3] = mImeWindowVis; 441 switches[4] = mImeBackDisposition; 442 binders.add(mImeToken); 443 } 444 switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0; 445 switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0; 446 } 447 448 /** 449 * The status bar service should call this each time the user brings the panel from 450 * invisible to visible in order to clear the notification light. 451 */ 452 public void onPanelRevealed() { 453 enforceStatusBarService(); 454 455 // tell the notification manager to turn off the lights. 456 mNotificationCallbacks.onPanelRevealed(); 457 } 458 459 public void onNotificationClick(String pkg, String tag, int id) { 460 enforceStatusBarService(); 461 462 mNotificationCallbacks.onNotificationClick(pkg, tag, id); 463 } 464 465 public void onNotificationError(String pkg, String tag, int id, 466 int uid, int initialPid, String message) { 467 enforceStatusBarService(); 468 469 // WARNING: this will call back into us to do the remove. Don't hold any locks. 470 mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message); 471 } 472 473 public void onNotificationClear(String pkg, String tag, int id) { 474 enforceStatusBarService(); 475 476 mNotificationCallbacks.onNotificationClear(pkg, tag, id); 477 } 478 479 public void onClearAllNotifications() { 480 enforceStatusBarService(); 481 482 mNotificationCallbacks.onClearAll(); 483 } 484 485 // ================================================================================ 486 // Callbacks for NotificationManagerService. 487 // ================================================================================ 488 public IBinder addNotification(StatusBarNotification notification) { 489 synchronized (mNotifications) { 490 IBinder key = new Binder(); 491 mNotifications.put(key, notification); 492 if (mBar != null) { 493 try { 494 mBar.addNotification(key, notification); 495 } catch (RemoteException ex) { 496 } 497 } 498 return key; 499 } 500 } 501 502 public void updateNotification(IBinder key, StatusBarNotification notification) { 503 synchronized (mNotifications) { 504 if (!mNotifications.containsKey(key)) { 505 throw new IllegalArgumentException("updateNotification key not found: " + key); 506 } 507 mNotifications.put(key, notification); 508 if (mBar != null) { 509 try { 510 mBar.updateNotification(key, notification); 511 } catch (RemoteException ex) { 512 } 513 } 514 } 515 } 516 517 public void removeNotification(IBinder key) { 518 synchronized (mNotifications) { 519 final StatusBarNotification n = mNotifications.remove(key); 520 if (n == null) { 521 Slog.e(TAG, "removeNotification key not found: " + key); 522 return; 523 } 524 if (mBar != null) { 525 try { 526 mBar.removeNotification(key); 527 } catch (RemoteException ex) { 528 } 529 } 530 } 531 } 532 533 // ================================================================================ 534 // Can be called from any thread 535 // ================================================================================ 536 537 // lock on mDisableRecords 538 void manageDisableListLocked(int userId, int what, IBinder token, String pkg) { 539 if (SPEW) { 540 Slog.d(TAG, "manageDisableList userId=" + userId 541 + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg); 542 } 543 // update the list 544 final int N = mDisableRecords.size(); 545 DisableRecord tok = null; 546 int i; 547 for (i=0; i<N; i++) { 548 DisableRecord t = mDisableRecords.get(i); 549 if (t.token == token && t.userId == userId) { 550 tok = t; 551 break; 552 } 553 } 554 if (what == 0 || !token.isBinderAlive()) { 555 if (tok != null) { 556 mDisableRecords.remove(i); 557 tok.token.unlinkToDeath(tok, 0); 558 } 559 } else { 560 if (tok == null) { 561 tok = new DisableRecord(); 562 tok.userId = userId; 563 try { 564 token.linkToDeath(tok, 0); 565 } 566 catch (RemoteException ex) { 567 return; // give up 568 } 569 mDisableRecords.add(tok); 570 } 571 tok.what = what; 572 tok.token = token; 573 tok.pkg = pkg; 574 } 575 } 576 577 // lock on mDisableRecords 578 int gatherDisableActionsLocked(int userId) { 579 final int N = mDisableRecords.size(); 580 // gather the new net flags 581 int net = 0; 582 for (int i=0; i<N; i++) { 583 final DisableRecord rec = mDisableRecords.get(i); 584 if (rec.userId == userId) { 585 net |= rec.what; 586 } 587 } 588 return net; 589 } 590 591 // ================================================================================ 592 // Always called from UI thread 593 // ================================================================================ 594 595 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 596 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 597 != PackageManager.PERMISSION_GRANTED) { 598 pw.println("Permission Denial: can't dump StatusBar from from pid=" 599 + Binder.getCallingPid() 600 + ", uid=" + Binder.getCallingUid()); 601 return; 602 } 603 604 synchronized (mIcons) { 605 mIcons.dump(pw); 606 } 607 608 synchronized (mNotifications) { 609 int i=0; 610 pw.println("Notification list:"); 611 for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) { 612 pw.printf(" %2d: %s\n", i, e.getValue().toString()); 613 i++; 614 } 615 } 616 617 synchronized (mLock) { 618 pw.println(" mDisabled=0x" + Integer.toHexString(mDisabled)); 619 final int N = mDisableRecords.size(); 620 pw.println(" mDisableRecords.size=" + N); 621 for (int i=0; i<N; i++) { 622 DisableRecord tok = mDisableRecords.get(i); 623 pw.println(" [" + i + "] userId=" + tok.userId 624 + " what=0x" + Integer.toHexString(tok.what) 625 + " pkg=" + tok.pkg 626 + " token=" + tok.token); 627 } 628 } 629 } 630 631 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 632 public void onReceive(Context context, Intent intent) { 633 String action = intent.getAction(); 634 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 635 || Intent.ACTION_SCREEN_OFF.equals(action)) { 636 collapsePanels(); 637 } 638 /* 639 else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { 640 updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false), 641 intent.getStringExtra(Telephony.Intents.EXTRA_SPN), 642 intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false), 643 intent.getStringExtra(Telephony.Intents.EXTRA_PLMN)); 644 } 645 else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { 646 updateResources(); 647 } 648 */ 649 } 650 }; 651 652 } 653