MediaProjectionManagerService.java revision 4444c5b27874866f18cd8f4abb8914cc17857ea7
1/* 2 * Copyright (C) 2014 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.media.projection; 18 19import com.android.server.Watchdog; 20 21import android.Manifest; 22import android.app.AppOpsManager; 23import android.content.Context; 24import android.content.pm.PackageManager; 25import android.hardware.display.DisplayManager; 26import android.media.MediaRouter; 27import android.media.projection.IMediaProjectionManager; 28import android.media.projection.IMediaProjection; 29import android.media.projection.IMediaProjectionCallback; 30import android.media.projection.IMediaProjectionWatcherCallback; 31import android.media.projection.MediaProjectionInfo; 32import android.media.projection.MediaProjectionManager; 33import android.os.Binder; 34import android.os.Handler; 35import android.os.IBinder; 36import android.os.IBinder.DeathRecipient; 37import android.os.Looper; 38import android.os.Message; 39import android.os.RemoteException; 40import android.os.UserHandle; 41import android.util.ArrayMap; 42import android.util.Slog; 43 44import com.android.server.SystemService; 45 46import java.io.FileDescriptor; 47import java.io.PrintWriter; 48import java.util.ArrayList; 49import java.util.Collection; 50import java.util.List; 51import java.util.Map; 52 53/** 54 * Manages MediaProjection sessions. 55 * 56 * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections, 57 * as well as the capabilities they grant. Any service using MediaProjection tokens as permission 58 * grants <b>must</b> validate the token before use by calling {@link 59 * IMediaProjectionService#isValidMediaProjection}. 60 */ 61public final class MediaProjectionManagerService extends SystemService 62 implements Watchdog.Monitor { 63 private static final String TAG = "MediaProjectionManagerService"; 64 65 private final Object mLock = new Object(); // Protects the list of media projections 66 private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters; 67 private final CallbackDelegate mCallbackDelegate; 68 69 private final Context mContext; 70 private final AppOpsManager mAppOps; 71 72 private final MediaRouter mMediaRouter; 73 private final MediaRouterCallback mMediaRouterCallback; 74 private MediaRouter.RouteInfo mMediaRouteInfo; 75 76 private IBinder mProjectionToken; 77 private MediaProjection mProjectionGrant; 78 79 public MediaProjectionManagerService(Context context) { 80 super(context); 81 mContext = context; 82 mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>(); 83 mCallbackDelegate = new CallbackDelegate(); 84 mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); 85 mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); 86 mMediaRouterCallback = new MediaRouterCallback(); 87 Watchdog.getInstance().addMonitor(this); 88 } 89 90 @Override 91 public void onStart() { 92 publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(), 93 false /*allowIsolated*/); 94 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback, 95 MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); 96 } 97 98 @Override 99 public void onSwitchUser(int userId) { 100 mMediaRouter.rebindAsUser(userId); 101 } 102 103 @Override 104 public void monitor() { 105 synchronized (mLock) { /* check for deadlock */ } 106 } 107 108 private void startProjectionLocked(final MediaProjection projection) { 109 if (mProjectionGrant != null) { 110 mProjectionGrant.stop(); 111 } 112 if (mMediaRouteInfo != null) { 113 mMediaRouter.getDefaultRoute().select(); 114 } 115 mProjectionToken = projection.asBinder(); 116 mProjectionGrant = projection; 117 dispatchStart(projection); 118 } 119 120 private void stopProjectionLocked(final MediaProjection projection) { 121 mProjectionToken = null; 122 mProjectionGrant = null; 123 dispatchStop(projection); 124 } 125 126 private void addCallback(final IMediaProjectionWatcherCallback callback) { 127 IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { 128 @Override 129 public void binderDied() { 130 synchronized (mLock) { 131 removeCallback(callback); 132 } 133 } 134 }; 135 synchronized (mLock) { 136 mCallbackDelegate.add(callback); 137 linkDeathRecipientLocked(callback, deathRecipient); 138 } 139 } 140 141 private void removeCallback(IMediaProjectionWatcherCallback callback) { 142 synchronized (mLock) { 143 unlinkDeathRecipientLocked(callback); 144 mCallbackDelegate.remove(callback); 145 } 146 } 147 148 private void linkDeathRecipientLocked(IMediaProjectionWatcherCallback callback, 149 IBinder.DeathRecipient deathRecipient) { 150 try { 151 final IBinder token = callback.asBinder(); 152 token.linkToDeath(deathRecipient, 0); 153 mDeathEaters.put(token, deathRecipient); 154 } catch (RemoteException e) { 155 Slog.e(TAG, "Unable to link to death for media projection monitoring callback", e); 156 } 157 } 158 159 private void unlinkDeathRecipientLocked(IMediaProjectionWatcherCallback callback) { 160 final IBinder token = callback.asBinder(); 161 IBinder.DeathRecipient deathRecipient = mDeathEaters.remove(token); 162 if (deathRecipient != null) { 163 token.unlinkToDeath(deathRecipient, 0); 164 } 165 } 166 167 private void dispatchStart(MediaProjection projection) { 168 mCallbackDelegate.dispatchStart(projection); 169 } 170 171 private void dispatchStop(MediaProjection projection) { 172 mCallbackDelegate.dispatchStop(projection); 173 } 174 175 private boolean isValidMediaProjection(IBinder token) { 176 synchronized (mLock) { 177 if (mProjectionToken != null) { 178 return mProjectionToken.equals(token); 179 } 180 return false; 181 } 182 } 183 184 private MediaProjectionInfo getActiveProjectionInfo() { 185 synchronized (mLock) { 186 if (mProjectionGrant == null) { 187 return null; 188 } 189 return mProjectionGrant.getProjectionInfo(); 190 } 191 } 192 193 private void dump(final PrintWriter pw) { 194 pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)"); 195 synchronized (mLock) { 196 pw.println("Media Projection: "); 197 if (mProjectionGrant != null ) { 198 mProjectionGrant.dump(pw); 199 } else { 200 pw.println("null"); 201 } 202 } 203 } 204 205 private final class BinderService extends IMediaProjectionManager.Stub { 206 207 @Override // Binder call 208 public boolean hasProjectionPermission(int uid, String packageName) { 209 long token = Binder.clearCallingIdentity(); 210 boolean hasPermission = false; 211 try { 212 hasPermission |= checkPermission(packageName, 213 android.Manifest.permission.CAPTURE_VIDEO_OUTPUT) 214 || mAppOps.noteOpNoThrow( 215 AppOpsManager.OP_PROJECT_MEDIA, uid, packageName) 216 == AppOpsManager.MODE_ALLOWED; 217 } finally { 218 Binder.restoreCallingIdentity(token); 219 } 220 return hasPermission; 221 } 222 223 @Override // Binder call 224 public IMediaProjection createProjection(int uid, String packageName, int type, 225 boolean isPermanentGrant) { 226 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) 227 != PackageManager.PERMISSION_GRANTED) { 228 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant " 229 + "projection permission"); 230 } 231 if (packageName == null || packageName.isEmpty()) { 232 throw new IllegalArgumentException("package name must not be empty"); 233 } 234 long callingToken = Binder.clearCallingIdentity(); 235 MediaProjection projection; 236 try { 237 projection = new MediaProjection(type, uid, packageName); 238 if (isPermanentGrant) { 239 mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA, 240 projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED); 241 } 242 } finally { 243 Binder.restoreCallingIdentity(callingToken); 244 } 245 return projection; 246 } 247 248 @Override // Binder call 249 public boolean isValidMediaProjection(IMediaProjection projection) { 250 return MediaProjectionManagerService.this.isValidMediaProjection( 251 projection.asBinder()); 252 } 253 254 @Override // Binder call 255 public MediaProjectionInfo getActiveProjectionInfo() { 256 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) 257 != PackageManager.PERMISSION_GRANTED) { 258 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add " 259 + "projection callbacks"); 260 } 261 final long token = Binder.clearCallingIdentity(); 262 try { 263 return MediaProjectionManagerService.this.getActiveProjectionInfo(); 264 } finally { 265 Binder.restoreCallingIdentity(token); 266 } 267 } 268 269 @Override // Binder call 270 public void stopActiveProjection() { 271 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) 272 != PackageManager.PERMISSION_GRANTED) { 273 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add " 274 + "projection callbacks"); 275 } 276 final long token = Binder.clearCallingIdentity(); 277 try { 278 if (mProjectionGrant != null) { 279 mProjectionGrant.stop(); 280 } 281 } finally { 282 Binder.restoreCallingIdentity(token); 283 } 284 285 } 286 287 @Override //Binder call 288 public void addCallback(final IMediaProjectionWatcherCallback callback) { 289 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) 290 != PackageManager.PERMISSION_GRANTED) { 291 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add " 292 + "projection callbacks"); 293 } 294 final long token = Binder.clearCallingIdentity(); 295 try { 296 MediaProjectionManagerService.this.addCallback(callback); 297 } finally { 298 Binder.restoreCallingIdentity(token); 299 } 300 } 301 302 @Override 303 public void removeCallback(IMediaProjectionWatcherCallback callback) { 304 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) 305 != PackageManager.PERMISSION_GRANTED) { 306 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove " 307 + "projection callbacks"); 308 } 309 final long token = Binder.clearCallingIdentity(); 310 try { 311 MediaProjectionManagerService.this.removeCallback(callback); 312 } finally { 313 Binder.restoreCallingIdentity(token); 314 } 315 } 316 317 @Override // Binder call 318 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { 319 if (mContext == null 320 || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) 321 != PackageManager.PERMISSION_GRANTED) { 322 pw.println("Permission Denial: can't dump MediaProjectionManager from from pid=" 323 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); 324 return; 325 } 326 327 final long token = Binder.clearCallingIdentity(); 328 try { 329 dump(pw); 330 } finally { 331 Binder.restoreCallingIdentity(token); 332 } 333 } 334 335 336 private boolean checkPermission(String packageName, String permission) { 337 return mContext.getPackageManager().checkPermission(permission, packageName) 338 == PackageManager.PERMISSION_GRANTED; 339 } 340 } 341 342 private final class MediaProjection extends IMediaProjection.Stub { 343 public final int uid; 344 public final String packageName; 345 public final UserHandle userHandle; 346 347 private IBinder mToken; 348 private IBinder.DeathRecipient mDeathEater; 349 private int mType; 350 351 public MediaProjection(int type, int uid, String packageName) { 352 mType = type; 353 this.uid = uid; 354 this.packageName = packageName; 355 userHandle = new UserHandle(UserHandle.getUserId(uid)); 356 } 357 358 @Override // Binder call 359 public boolean canProjectVideo() { 360 return mType == MediaProjectionManager.TYPE_MIRRORING || 361 mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE; 362 } 363 364 @Override // Binder call 365 public boolean canProjectSecureVideo() { 366 return false; 367 } 368 369 @Override // Binder call 370 public boolean canProjectAudio() { 371 return mType == MediaProjectionManager.TYPE_MIRRORING || 372 mType == MediaProjectionManager.TYPE_PRESENTATION; 373 } 374 375 @Override // Binder call 376 public int applyVirtualDisplayFlags(int flags) { 377 if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) { 378 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; 379 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR 380 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; 381 return flags; 382 } else if (mType == MediaProjectionManager.TYPE_MIRRORING) { 383 flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | 384 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR); 385 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | 386 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; 387 return flags; 388 } else if (mType == MediaProjectionManager.TYPE_PRESENTATION) { 389 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; 390 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | 391 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION | 392 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; 393 return flags; 394 } else { 395 throw new RuntimeException("Unknown MediaProjection type"); 396 } 397 } 398 399 @Override // Binder call 400 public void start(final IMediaProjectionCallback callback) { 401 if (callback == null) { 402 throw new IllegalArgumentException("callback must not be null"); 403 } 404 synchronized (mLock) { 405 if (isValidMediaProjection(asBinder())) { 406 throw new IllegalStateException( 407 "Cannot start already started MediaProjection"); 408 } 409 registerCallback(callback); 410 try { 411 mToken = callback.asBinder(); 412 mDeathEater = new IBinder.DeathRecipient() { 413 @Override 414 public void binderDied() { 415 mCallbackDelegate.remove(callback); 416 stop(); 417 } 418 }; 419 mToken.linkToDeath(mDeathEater, 0); 420 } catch (RemoteException e) { 421 Slog.w(TAG, 422 "MediaProjectionCallbacks must be valid, aborting MediaProjection", e); 423 return; 424 } 425 startProjectionLocked(this); 426 } 427 } 428 429 @Override // Binder call 430 public void stop() { 431 synchronized (mLock) { 432 if (!isValidMediaProjection(asBinder())) { 433 Slog.w(TAG, "Attempted to stop inactive MediaProjection " 434 + "(uid=" + Binder.getCallingUid() + ", " 435 + "pid=" + Binder.getCallingPid() + ")"); 436 return; 437 } 438 mToken.unlinkToDeath(mDeathEater, 0); 439 stopProjectionLocked(this); 440 } 441 } 442 443 @Override 444 public void registerCallback(IMediaProjectionCallback callback) { 445 if (callback == null) { 446 throw new IllegalArgumentException("callback must not be null"); 447 } 448 mCallbackDelegate.add(callback); 449 } 450 451 @Override 452 public void unregisterCallback(IMediaProjectionCallback callback) { 453 if (callback == null) { 454 throw new IllegalArgumentException("callback must not be null"); 455 } 456 mCallbackDelegate.remove(callback); 457 } 458 459 public MediaProjectionInfo getProjectionInfo() { 460 return new MediaProjectionInfo(packageName, userHandle); 461 } 462 463 public void dump(PrintWriter pw) { 464 pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType)); 465 } 466 } 467 468 private class MediaRouterCallback extends MediaRouter.SimpleCallback { 469 @Override 470 public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) { 471 synchronized (mLock) { 472 if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) { 473 mMediaRouteInfo = info; 474 if (mProjectionGrant != null) { 475 mProjectionGrant.stop(); 476 } 477 } 478 } 479 } 480 481 @Override 482 public void onRouteUnselected(MediaRouter route, int type, MediaRouter.RouteInfo info) { 483 if (mMediaRouteInfo == info) { 484 mMediaRouteInfo = null; 485 } 486 } 487 } 488 489 490 private static class CallbackDelegate { 491 private Map<IBinder, IMediaProjectionCallback> mClientCallbacks; 492 private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks; 493 private Handler mHandler; 494 private Object mLock = new Object(); 495 496 public CallbackDelegate() { 497 mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/); 498 mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>(); 499 mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>(); 500 } 501 502 public void add(IMediaProjectionCallback callback) { 503 synchronized (mLock) { 504 mClientCallbacks.put(callback.asBinder(), callback); 505 } 506 } 507 508 public void add(IMediaProjectionWatcherCallback callback) { 509 synchronized (mLock) { 510 mWatcherCallbacks.put(callback.asBinder(), callback); 511 } 512 } 513 514 public void remove(IMediaProjectionCallback callback) { 515 synchronized (mLock) { 516 mClientCallbacks.remove(callback.asBinder()); 517 } 518 } 519 520 public void remove(IMediaProjectionWatcherCallback callback) { 521 synchronized (mLock) { 522 mWatcherCallbacks.remove(callback.asBinder()); 523 } 524 } 525 526 public void dispatchStart(MediaProjection projection) { 527 if (projection == null) { 528 Slog.e(TAG, "Tried to dispatch start notification for a null media projection." 529 + " Ignoring!"); 530 return; 531 } 532 synchronized (mLock) { 533 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { 534 MediaProjectionInfo info = projection.getProjectionInfo(); 535 mHandler.post(new WatcherStartCallback(info, callback)); 536 } 537 } 538 } 539 540 public void dispatchStop(MediaProjection projection) { 541 if (projection == null) { 542 Slog.e(TAG, "Tried to dispatch stop notification for a null media projection." 543 + " Ignoring!"); 544 return; 545 } 546 synchronized (mLock) { 547 for (IMediaProjectionCallback callback : mClientCallbacks.values()) { 548 mHandler.post(new ClientStopCallback(callback)); 549 } 550 551 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { 552 MediaProjectionInfo info = projection.getProjectionInfo(); 553 mHandler.post(new WatcherStopCallback(info, callback)); 554 } 555 } 556 } 557 } 558 559 private static final class WatcherStartCallback implements Runnable { 560 private IMediaProjectionWatcherCallback mCallback; 561 private MediaProjectionInfo mInfo; 562 563 public WatcherStartCallback(MediaProjectionInfo info, 564 IMediaProjectionWatcherCallback callback) { 565 mInfo = info; 566 mCallback = callback; 567 } 568 569 @Override 570 public void run() { 571 try { 572 mCallback.onStart(mInfo); 573 } catch (RemoteException e) { 574 Slog.w(TAG, "Failed to notify media projection has stopped", e); 575 } 576 } 577 } 578 579 private static final class WatcherStopCallback implements Runnable { 580 private IMediaProjectionWatcherCallback mCallback; 581 private MediaProjectionInfo mInfo; 582 583 public WatcherStopCallback(MediaProjectionInfo info, 584 IMediaProjectionWatcherCallback callback) { 585 mInfo = info; 586 mCallback = callback; 587 } 588 589 @Override 590 public void run() { 591 try { 592 mCallback.onStop(mInfo); 593 } catch (RemoteException e) { 594 Slog.w(TAG, "Failed to notify media projection has stopped", e); 595 } 596 } 597 } 598 599 private static final class ClientStopCallback implements Runnable { 600 private IMediaProjectionCallback mCallback; 601 602 public ClientStopCallback(IMediaProjectionCallback callback) { 603 mCallback = callback; 604 } 605 606 @Override 607 public void run() { 608 try { 609 mCallback.onStop(); 610 } catch (RemoteException e) { 611 Slog.w(TAG, "Failed to notify media projection has stopped", e); 612 } 613 } 614 } 615 616 617 private static String typeToString(int type) { 618 switch (type) { 619 case MediaProjectionManager.TYPE_SCREEN_CAPTURE: 620 return "TYPE_SCREEN_CAPTURE"; 621 case MediaProjectionManager.TYPE_MIRRORING: 622 return "TYPE_MIRRORING"; 623 case MediaProjectionManager.TYPE_PRESENTATION: 624 return "TYPE_PRESENTATION"; 625 } 626 return Integer.toString(type); 627 } 628} 629