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