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