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