blob: fccff57e32349d0aae320ff555509574620c3b56 [file] [log] [blame]
Michael Wrightc39d47a2014-07-08 18:07:36 -07001/*
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;
Michael Wright89c2cb62014-09-14 13:06:34 -070026import android.media.MediaRouter;
Michael Wrightc39d47a2014-07-08 18:07:36 -070027import android.media.projection.IMediaProjectionManager;
28import android.media.projection.IMediaProjection;
29import android.media.projection.IMediaProjectionCallback;
Michael Wrightd86ecd22014-08-12 19:27:54 -070030import android.media.projection.IMediaProjectionWatcherCallback;
31import android.media.projection.MediaProjectionInfo;
Michael Wrightc39d47a2014-07-08 18:07:36 -070032import android.media.projection.MediaProjectionManager;
33import android.os.Binder;
34import android.os.Handler;
35import android.os.IBinder;
Michael Wrightc39d47a2014-07-08 18:07:36 -070036import android.os.Looper;
Michael Wrightc39d47a2014-07-08 18:07:36 -070037import android.os.RemoteException;
Michael Wrightd86ecd22014-08-12 19:27:54 -070038import android.os.UserHandle;
Michael Wrightc39d47a2014-07-08 18:07:36 -070039import android.util.ArrayMap;
40import android.util.Slog;
41
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060042import com.android.internal.util.DumpUtils;
Michael Wrightc39d47a2014-07-08 18:07:36 -070043import com.android.server.SystemService;
44
45import java.io.FileDescriptor;
46import java.io.PrintWriter;
Michael Wrightc39d47a2014-07-08 18:07:36 -070047import java.util.Map;
48
49/**
50 * Manages MediaProjection sessions.
51 *
52 * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
53 * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
54 * grants <b>must</b> validate the token before use by calling {@link
55 * IMediaProjectionService#isValidMediaProjection}.
56 */
57public final class MediaProjectionManagerService extends SystemService
58 implements Watchdog.Monitor {
59 private static final String TAG = "MediaProjectionManagerService";
60
61 private final Object mLock = new Object(); // Protects the list of media projections
Michael Wrightd86ecd22014-08-12 19:27:54 -070062 private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;
63 private final CallbackDelegate mCallbackDelegate;
Michael Wrightc39d47a2014-07-08 18:07:36 -070064
65 private final Context mContext;
66 private final AppOpsManager mAppOps;
67
Michael Wright89c2cb62014-09-14 13:06:34 -070068 private final MediaRouter mMediaRouter;
69 private final MediaRouterCallback mMediaRouterCallback;
70 private MediaRouter.RouteInfo mMediaRouteInfo;
71
Michael Wrightd86ecd22014-08-12 19:27:54 -070072 private IBinder mProjectionToken;
73 private MediaProjection mProjectionGrant;
74
Michael Wrightc39d47a2014-07-08 18:07:36 -070075 public MediaProjectionManagerService(Context context) {
76 super(context);
77 mContext = context;
Michael Wrightd86ecd22014-08-12 19:27:54 -070078 mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
79 mCallbackDelegate = new CallbackDelegate();
Michael Wrightc39d47a2014-07-08 18:07:36 -070080 mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
Michael Wright89c2cb62014-09-14 13:06:34 -070081 mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
82 mMediaRouterCallback = new MediaRouterCallback();
Michael Wrightc39d47a2014-07-08 18:07:36 -070083 Watchdog.getInstance().addMonitor(this);
84 }
85
86 @Override
87 public void onStart() {
88 publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
89 false /*allowIsolated*/);
Jason Monk4444c5b2014-10-27 19:20:02 -040090 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
91 MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
Michael Wright89c2cb62014-09-14 13:06:34 -070092 }
93
94 @Override
95 public void onSwitchUser(int userId) {
96 mMediaRouter.rebindAsUser(userId);
Michael Wright05aab582015-02-06 14:08:13 -080097 synchronized (mLock) {
98 if (mProjectionGrant != null) {
99 mProjectionGrant.stop();
100 }
101 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700102 }
103
104 @Override
105 public void monitor() {
106 synchronized (mLock) { /* check for deadlock */ }
107 }
108
Michael Wrightd86ecd22014-08-12 19:27:54 -0700109 private void startProjectionLocked(final MediaProjection projection) {
110 if (mProjectionGrant != null) {
111 mProjectionGrant.stop();
112 }
Michael Wright89c2cb62014-09-14 13:06:34 -0700113 if (mMediaRouteInfo != null) {
Sungsoo Lim59579ce2017-07-14 10:28:51 -0700114 mMediaRouter.getFallbackRoute().select();
Michael Wright89c2cb62014-09-14 13:06:34 -0700115 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700116 mProjectionToken = projection.asBinder();
117 mProjectionGrant = projection;
118 dispatchStart(projection);
119 }
120
121 private void stopProjectionLocked(final MediaProjection projection) {
122 mProjectionToken = null;
123 mProjectionGrant = null;
124 dispatchStop(projection);
125 }
126
127 private void addCallback(final IMediaProjectionWatcherCallback callback) {
128 IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
129 @Override
130 public void binderDied() {
Jae Seoac3f8e52015-08-04 11:12:13 -0700131 removeCallback(callback);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700132 }
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);
John Spurlock78b8c8f2014-08-25 17:52:06 -0400143 mCallbackDelegate.remove(callback);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700144 }
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
Michael Wrightc39d47a2014-07-08 18:07:36 -0700192 private void dump(final PrintWriter pw) {
193 pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
194 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700195 pw.println("Media Projection: ");
196 if (mProjectionGrant != null ) {
197 mProjectionGrant.dump(pw);
198 } else {
199 pw.println("null");
Michael Wrightc39d47a2014-07-08 18:07:36 -0700200 }
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)
Michael Wright6720be42014-07-29 19:14:16 -0700213 || mAppOps.noteOpNoThrow(
Michael Wrightc39d47a2014-07-08 18:07:36 -0700214 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) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700225 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
Michael Wrightc39d47a2014-07-08 18:07:36 -0700226 != PackageManager.PERMISSION_GRANTED) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700227 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
Michael Wrightc39d47a2014-07-08 18:07:36 -0700228 + "projection permission");
229 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700230 if (packageName == null || packageName.isEmpty()) {
231 throw new IllegalArgumentException("package name must not be empty");
232 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700233 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) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700249 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 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700314 }
315
316 @Override // Binder call
317 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600318 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700319 final long token = Binder.clearCallingIdentity();
320 try {
John Spurlock9b843092014-10-20 13:37:48 -0400321 MediaProjectionManagerService.this.dump(pw);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700322 } finally {
323 Binder.restoreCallingIdentity(token);
324 }
325 }
326
Michael Wrightd86ecd22014-08-12 19:27:54 -0700327
Michael Wrightc39d47a2014-07-08 18:07:36 -0700328 private boolean checkPermission(String packageName, String permission) {
329 return mContext.getPackageManager().checkPermission(permission, packageName)
330 == PackageManager.PERMISSION_GRANTED;
331 }
332 }
333
Michael Wrightd86ecd22014-08-12 19:27:54 -0700334 private final class MediaProjection extends IMediaProjection.Stub {
335 public final int uid;
336 public final String packageName;
337 public final UserHandle userHandle;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700338
Jae Seoac3f8e52015-08-04 11:12:13 -0700339 private IMediaProjectionCallback mCallback;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700340 private IBinder mToken;
Michael Wrightd86ecd22014-08-12 19:27:54 -0700341 private IBinder.DeathRecipient mDeathEater;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700342 private int mType;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700343
344 public MediaProjection(int type, int uid, String packageName) {
345 mType = type;
346 this.uid = uid;
347 this.packageName = packageName;
Michael Wrightd86ecd22014-08-12 19:27:54 -0700348 userHandle = new UserHandle(UserHandle.getUserId(uid));
Michael Wrightc39d47a2014-07-08 18:07:36 -0700349 }
350
351 @Override // Binder call
352 public boolean canProjectVideo() {
353 return mType == MediaProjectionManager.TYPE_MIRRORING ||
354 mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
355 }
356
357 @Override // Binder call
358 public boolean canProjectSecureVideo() {
359 return false;
360 }
361
362 @Override // Binder call
363 public boolean canProjectAudio() {
364 return mType == MediaProjectionManager.TYPE_MIRRORING ||
365 mType == MediaProjectionManager.TYPE_PRESENTATION;
366 }
367
368 @Override // Binder call
Michael Wright6720be42014-07-29 19:14:16 -0700369 public int applyVirtualDisplayFlags(int flags) {
370 if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
371 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
372 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
373 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
374 return flags;
375 } else if (mType == MediaProjectionManager.TYPE_MIRRORING) {
376 flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
377 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
378 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
379 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
380 return flags;
381 } else if (mType == MediaProjectionManager.TYPE_PRESENTATION) {
382 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
383 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
384 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
385 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
386 return flags;
387 } else {
388 throw new RuntimeException("Unknown MediaProjection type");
Michael Wrightc39d47a2014-07-08 18:07:36 -0700389 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700390 }
391
392 @Override // Binder call
Michael Wrightd86ecd22014-08-12 19:27:54 -0700393 public void start(final IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700394 if (callback == null) {
395 throw new IllegalArgumentException("callback must not be null");
396 }
397 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700398 if (isValidMediaProjection(asBinder())) {
Zimuzoef531e02018-11-01 16:04:08 +0000399 Slog.w(TAG, "UID " + Binder.getCallingUid()
400 + " attempted to start already started MediaProjection");
401 return;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700402 }
Jae Seoac3f8e52015-08-04 11:12:13 -0700403 mCallback = callback;
404 registerCallback(mCallback);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700405 try {
406 mToken = callback.asBinder();
Michael Wrightd86ecd22014-08-12 19:27:54 -0700407 mDeathEater = new IBinder.DeathRecipient() {
408 @Override
409 public void binderDied() {
410 mCallbackDelegate.remove(callback);
411 stop();
412 }
413 };
414 mToken.linkToDeath(mDeathEater, 0);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700415 } catch (RemoteException e) {
416 Slog.w(TAG,
417 "MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
418 return;
419 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700420 startProjectionLocked(this);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700421 }
422 }
423
424 @Override // Binder call
425 public void stop() {
426 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700427 if (!isValidMediaProjection(asBinder())) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700428 Slog.w(TAG, "Attempted to stop inactive MediaProjection "
429 + "(uid=" + Binder.getCallingUid() + ", "
430 + "pid=" + Binder.getCallingPid() + ")");
431 return;
432 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700433 stopProjectionLocked(this);
Jae Seoac3f8e52015-08-04 11:12:13 -0700434 mToken.unlinkToDeath(mDeathEater, 0);
435 mToken = null;
436 unregisterCallback(mCallback);
437 mCallback = null;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700438 }
439 }
440
441 @Override
Michael Wrightcde5bb42014-09-08 13:26:34 -0700442 public void registerCallback(IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700443 if (callback == null) {
444 throw new IllegalArgumentException("callback must not be null");
445 }
446 mCallbackDelegate.add(callback);
447 }
448
449 @Override
Michael Wrightcde5bb42014-09-08 13:26:34 -0700450 public void unregisterCallback(IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700451 if (callback == null) {
452 throw new IllegalArgumentException("callback must not be null");
453 }
454 mCallbackDelegate.remove(callback);
455 }
456
Michael Wrightd86ecd22014-08-12 19:27:54 -0700457 public MediaProjectionInfo getProjectionInfo() {
458 return new MediaProjectionInfo(packageName, userHandle);
459 }
460
461 public void dump(PrintWriter pw) {
462 pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
Michael Wrightc39d47a2014-07-08 18:07:36 -0700463 }
464 }
465
Michael Wright89c2cb62014-09-14 13:06:34 -0700466 private class MediaRouterCallback extends MediaRouter.SimpleCallback {
467 @Override
468 public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) {
469 synchronized (mLock) {
470 if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
471 mMediaRouteInfo = info;
472 if (mProjectionGrant != null) {
473 mProjectionGrant.stop();
474 }
475 }
476 }
477 }
478
479 @Override
480 public void onRouteUnselected(MediaRouter route, int type, MediaRouter.RouteInfo info) {
481 if (mMediaRouteInfo == info) {
482 mMediaRouteInfo = null;
483 }
484 }
485 }
486
Michael Wrightd86ecd22014-08-12 19:27:54 -0700487
Michael Wrightc39d47a2014-07-08 18:07:36 -0700488 private static class CallbackDelegate {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700489 private Map<IBinder, IMediaProjectionCallback> mClientCallbacks;
490 private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700491 private Handler mHandler;
492 private Object mLock = new Object();
493
494 public CallbackDelegate() {
495 mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700496 mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
497 mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
Michael Wrightc39d47a2014-07-08 18:07:36 -0700498 }
499
500 public void add(IMediaProjectionCallback callback) {
501 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700502 mClientCallbacks.put(callback.asBinder(), callback);
503 }
504 }
505
506 public void add(IMediaProjectionWatcherCallback callback) {
507 synchronized (mLock) {
508 mWatcherCallbacks.put(callback.asBinder(), callback);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700509 }
510 }
511
512 public void remove(IMediaProjectionCallback callback) {
513 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700514 mClientCallbacks.remove(callback.asBinder());
Michael Wrightc39d47a2014-07-08 18:07:36 -0700515 }
516 }
517
Michael Wrightd86ecd22014-08-12 19:27:54 -0700518 public void remove(IMediaProjectionWatcherCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700519 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700520 mWatcherCallbacks.remove(callback.asBinder());
521 }
522 }
523
524 public void dispatchStart(MediaProjection projection) {
525 if (projection == null) {
526 Slog.e(TAG, "Tried to dispatch start notification for a null media projection."
527 + " Ignoring!");
528 return;
529 }
530 synchronized (mLock) {
531 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
532 MediaProjectionInfo info = projection.getProjectionInfo();
533 mHandler.post(new WatcherStartCallback(info, callback));
534 }
535 }
536 }
537
538 public void dispatchStop(MediaProjection projection) {
539 if (projection == null) {
540 Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
541 + " Ignoring!");
542 return;
543 }
544 synchronized (mLock) {
545 for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
546 mHandler.post(new ClientStopCallback(callback));
547 }
548
549 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
550 MediaProjectionInfo info = projection.getProjectionInfo();
551 mHandler.post(new WatcherStopCallback(info, callback));
Michael Wrightc39d47a2014-07-08 18:07:36 -0700552 }
553 }
554 }
555 }
556
Michael Wrightd86ecd22014-08-12 19:27:54 -0700557 private static final class WatcherStartCallback implements Runnable {
558 private IMediaProjectionWatcherCallback mCallback;
559 private MediaProjectionInfo mInfo;
560
561 public WatcherStartCallback(MediaProjectionInfo info,
562 IMediaProjectionWatcherCallback callback) {
563 mInfo = info;
564 mCallback = callback;
565 }
566
567 @Override
568 public void run() {
569 try {
570 mCallback.onStart(mInfo);
571 } catch (RemoteException e) {
572 Slog.w(TAG, "Failed to notify media projection has stopped", e);
573 }
574 }
575 }
576
577 private static final class WatcherStopCallback implements Runnable {
578 private IMediaProjectionWatcherCallback mCallback;
579 private MediaProjectionInfo mInfo;
580
581 public WatcherStopCallback(MediaProjectionInfo info,
582 IMediaProjectionWatcherCallback callback) {
583 mInfo = info;
584 mCallback = callback;
585 }
586
587 @Override
588 public void run() {
589 try {
590 mCallback.onStop(mInfo);
591 } catch (RemoteException e) {
592 Slog.w(TAG, "Failed to notify media projection has stopped", e);
593 }
594 }
595 }
596
597 private static final class ClientStopCallback implements Runnable {
598 private IMediaProjectionCallback mCallback;
599
600 public ClientStopCallback(IMediaProjectionCallback callback) {
601 mCallback = callback;
602 }
603
604 @Override
605 public void run() {
606 try {
607 mCallback.onStop();
608 } catch (RemoteException e) {
609 Slog.w(TAG, "Failed to notify media projection has stopped", e);
610 }
611 }
612 }
613
614
Michael Wrightc39d47a2014-07-08 18:07:36 -0700615 private static String typeToString(int type) {
616 switch (type) {
617 case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
618 return "TYPE_SCREEN_CAPTURE";
619 case MediaProjectionManager.TYPE_MIRRORING:
620 return "TYPE_MIRRORING";
621 case MediaProjectionManager.TYPE_PRESENTATION:
622 return "TYPE_PRESENTATION";
623 }
624 return Integer.toString(type);
625 }
626}