blob: 270fbc68e1436d6980e1dc563cfcfac3ff61586c [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
Michael Wrightc39d47a2014-07-08 18:07:36 -070019import android.Manifest;
20import android.app.AppOpsManager;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.hardware.display.DisplayManager;
Michael Wright89c2cb62014-09-14 13:06:34 -070024import android.media.MediaRouter;
Michael Wrightc39d47a2014-07-08 18:07:36 -070025import android.media.projection.IMediaProjection;
26import android.media.projection.IMediaProjectionCallback;
Kevin Rocard92488ea2019-02-21 11:01:25 -080027import android.media.projection.IMediaProjectionManager;
Michael Wrightd86ecd22014-08-12 19:27:54 -070028import android.media.projection.IMediaProjectionWatcherCallback;
29import android.media.projection.MediaProjectionInfo;
Michael Wrightc39d47a2014-07-08 18:07:36 -070030import android.media.projection.MediaProjectionManager;
31import android.os.Binder;
32import android.os.Handler;
33import android.os.IBinder;
Michael Wrightc39d47a2014-07-08 18:07:36 -070034import android.os.Looper;
Michael Wrightc39d47a2014-07-08 18:07:36 -070035import android.os.RemoteException;
Michael Wrightd86ecd22014-08-12 19:27:54 -070036import android.os.UserHandle;
Michael Wrightc39d47a2014-07-08 18:07:36 -070037import android.util.ArrayMap;
38import android.util.Slog;
39
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060040import com.android.internal.util.DumpUtils;
Michael Wrightc39d47a2014-07-08 18:07:36 -070041import com.android.server.SystemService;
Kevin Rocard92488ea2019-02-21 11:01:25 -080042import com.android.server.Watchdog;
Michael Wrightc39d47a2014-07-08 18:07:36 -070043
44import java.io.FileDescriptor;
45import java.io.PrintWriter;
Michael Wrightc39d47a2014-07-08 18:07:36 -070046import java.util.Map;
47
48/**
49 * Manages MediaProjection sessions.
50 *
51 * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
52 * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
53 * grants <b>must</b> validate the token before use by calling {@link
54 * IMediaProjectionService#isValidMediaProjection}.
55 */
56public final class MediaProjectionManagerService extends SystemService
57 implements Watchdog.Monitor {
58 private static final String TAG = "MediaProjectionManagerService";
59
60 private final Object mLock = new Object(); // Protects the list of media projections
Michael Wrightd86ecd22014-08-12 19:27:54 -070061 private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;
62 private final CallbackDelegate mCallbackDelegate;
Michael Wrightc39d47a2014-07-08 18:07:36 -070063
64 private final Context mContext;
65 private final AppOpsManager mAppOps;
66
Michael Wright89c2cb62014-09-14 13:06:34 -070067 private final MediaRouter mMediaRouter;
68 private final MediaRouterCallback mMediaRouterCallback;
69 private MediaRouter.RouteInfo mMediaRouteInfo;
70
Michael Wrightd86ecd22014-08-12 19:27:54 -070071 private IBinder mProjectionToken;
72 private MediaProjection mProjectionGrant;
73
Michael Wrightc39d47a2014-07-08 18:07:36 -070074 public MediaProjectionManagerService(Context context) {
75 super(context);
76 mContext = context;
Michael Wrightd86ecd22014-08-12 19:27:54 -070077 mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
78 mCallbackDelegate = new CallbackDelegate();
Michael Wrightc39d47a2014-07-08 18:07:36 -070079 mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
Michael Wright89c2cb62014-09-14 13:06:34 -070080 mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
81 mMediaRouterCallback = new MediaRouterCallback();
Michael Wrightc39d47a2014-07-08 18:07:36 -070082 Watchdog.getInstance().addMonitor(this);
83 }
84
85 @Override
86 public void onStart() {
87 publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
88 false /*allowIsolated*/);
Jason Monk4444c5b2014-10-27 19:20:02 -040089 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
90 MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
Michael Wright89c2cb62014-09-14 13:06:34 -070091 }
92
93 @Override
94 public void onSwitchUser(int userId) {
95 mMediaRouter.rebindAsUser(userId);
Michael Wright05aab582015-02-06 14:08:13 -080096 synchronized (mLock) {
97 if (mProjectionGrant != null) {
98 mProjectionGrant.stop();
99 }
100 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700101 }
102
103 @Override
104 public void monitor() {
105 synchronized (mLock) { /* check for deadlock */ }
106 }
107
Michael Wrightd86ecd22014-08-12 19:27:54 -0700108 private void startProjectionLocked(final MediaProjection projection) {
109 if (mProjectionGrant != null) {
110 mProjectionGrant.stop();
111 }
Michael Wright89c2cb62014-09-14 13:06:34 -0700112 if (mMediaRouteInfo != null) {
Sungsoo Lim59579ce2017-07-14 10:28:51 -0700113 mMediaRouter.getFallbackRoute().select();
Michael Wright89c2cb62014-09-14 13:06:34 -0700114 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700115 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() {
Jae Seoac3f8e52015-08-04 11:12:13 -0700130 removeCallback(callback);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700131 }
132 };
133 synchronized (mLock) {
134 mCallbackDelegate.add(callback);
135 linkDeathRecipientLocked(callback, deathRecipient);
136 }
137 }
138
139 private void removeCallback(IMediaProjectionWatcherCallback callback) {
140 synchronized (mLock) {
141 unlinkDeathRecipientLocked(callback);
John Spurlock78b8c8f2014-08-25 17:52:06 -0400142 mCallbackDelegate.remove(callback);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700143 }
144 }
145
146 private void linkDeathRecipientLocked(IMediaProjectionWatcherCallback callback,
147 IBinder.DeathRecipient deathRecipient) {
148 try {
149 final IBinder token = callback.asBinder();
150 token.linkToDeath(deathRecipient, 0);
151 mDeathEaters.put(token, deathRecipient);
152 } catch (RemoteException e) {
153 Slog.e(TAG, "Unable to link to death for media projection monitoring callback", e);
154 }
155 }
156
157 private void unlinkDeathRecipientLocked(IMediaProjectionWatcherCallback callback) {
158 final IBinder token = callback.asBinder();
159 IBinder.DeathRecipient deathRecipient = mDeathEaters.remove(token);
160 if (deathRecipient != null) {
161 token.unlinkToDeath(deathRecipient, 0);
162 }
163 }
164
165 private void dispatchStart(MediaProjection projection) {
166 mCallbackDelegate.dispatchStart(projection);
167 }
168
169 private void dispatchStop(MediaProjection projection) {
170 mCallbackDelegate.dispatchStop(projection);
171 }
172
173 private boolean isValidMediaProjection(IBinder token) {
174 synchronized (mLock) {
175 if (mProjectionToken != null) {
176 return mProjectionToken.equals(token);
177 }
178 return false;
179 }
180 }
181
182 private MediaProjectionInfo getActiveProjectionInfo() {
183 synchronized (mLock) {
184 if (mProjectionGrant == null) {
185 return null;
186 }
187 return mProjectionGrant.getProjectionInfo();
188 }
189 }
190
Michael Wrightc39d47a2014-07-08 18:07:36 -0700191 private void dump(final PrintWriter pw) {
192 pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
193 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700194 pw.println("Media Projection: ");
195 if (mProjectionGrant != null ) {
196 mProjectionGrant.dump(pw);
197 } else {
198 pw.println("null");
Michael Wrightc39d47a2014-07-08 18:07:36 -0700199 }
200 }
201 }
202
203 private final class BinderService extends IMediaProjectionManager.Stub {
204
205 @Override // Binder call
206 public boolean hasProjectionPermission(int uid, String packageName) {
207 long token = Binder.clearCallingIdentity();
208 boolean hasPermission = false;
209 try {
210 hasPermission |= checkPermission(packageName,
211 android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
Michael Wright6720be42014-07-29 19:14:16 -0700212 || mAppOps.noteOpNoThrow(
Michael Wrightc39d47a2014-07-08 18:07:36 -0700213 AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
214 == AppOpsManager.MODE_ALLOWED;
215 } finally {
216 Binder.restoreCallingIdentity(token);
217 }
218 return hasPermission;
219 }
220
221 @Override // Binder call
222 public IMediaProjection createProjection(int uid, String packageName, int type,
223 boolean isPermanentGrant) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700224 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
Michael Wrightc39d47a2014-07-08 18:07:36 -0700225 != PackageManager.PERMISSION_GRANTED) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700226 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
Michael Wrightc39d47a2014-07-08 18:07:36 -0700227 + "projection permission");
228 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700229 if (packageName == null || packageName.isEmpty()) {
230 throw new IllegalArgumentException("package name must not be empty");
231 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700232 long callingToken = Binder.clearCallingIdentity();
233 MediaProjection projection;
234 try {
235 projection = new MediaProjection(type, uid, packageName);
236 if (isPermanentGrant) {
237 mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
238 projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
239 }
240 } finally {
241 Binder.restoreCallingIdentity(callingToken);
242 }
243 return projection;
244 }
245
246 @Override // Binder call
247 public boolean isValidMediaProjection(IMediaProjection projection) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700248 return MediaProjectionManagerService.this.isValidMediaProjection(
249 projection.asBinder());
250 }
251
252 @Override // Binder call
253 public MediaProjectionInfo getActiveProjectionInfo() {
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 return MediaProjectionManagerService.this.getActiveProjectionInfo();
262 } finally {
263 Binder.restoreCallingIdentity(token);
264 }
265 }
266
267 @Override // Binder call
268 public void stopActiveProjection() {
269 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
270 != PackageManager.PERMISSION_GRANTED) {
271 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
272 + "projection callbacks");
273 }
274 final long token = Binder.clearCallingIdentity();
275 try {
276 if (mProjectionGrant != null) {
277 mProjectionGrant.stop();
278 }
279 } finally {
280 Binder.restoreCallingIdentity(token);
281 }
282
283 }
284
285 @Override //Binder call
286 public void addCallback(final 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 add "
290 + "projection callbacks");
291 }
292 final long token = Binder.clearCallingIdentity();
293 try {
294 MediaProjectionManagerService.this.addCallback(callback);
295 } finally {
296 Binder.restoreCallingIdentity(token);
297 }
298 }
299
300 @Override
301 public void removeCallback(IMediaProjectionWatcherCallback callback) {
302 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
303 != PackageManager.PERMISSION_GRANTED) {
304 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove "
305 + "projection callbacks");
306 }
307 final long token = Binder.clearCallingIdentity();
308 try {
309 MediaProjectionManagerService.this.removeCallback(callback);
310 } finally {
311 Binder.restoreCallingIdentity(token);
312 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700313 }
314
315 @Override // Binder call
316 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600317 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700318 final long token = Binder.clearCallingIdentity();
319 try {
John Spurlock9b843092014-10-20 13:37:48 -0400320 MediaProjectionManagerService.this.dump(pw);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700321 } finally {
322 Binder.restoreCallingIdentity(token);
323 }
324 }
325
Michael Wrightd86ecd22014-08-12 19:27:54 -0700326
Michael Wrightc39d47a2014-07-08 18:07:36 -0700327 private boolean checkPermission(String packageName, String permission) {
328 return mContext.getPackageManager().checkPermission(permission, packageName)
329 == PackageManager.PERMISSION_GRANTED;
330 }
331 }
332
Michael Wrightd86ecd22014-08-12 19:27:54 -0700333 private final class MediaProjection extends IMediaProjection.Stub {
334 public final int uid;
335 public final String packageName;
336 public final UserHandle userHandle;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700337
Jae Seoac3f8e52015-08-04 11:12:13 -0700338 private IMediaProjectionCallback mCallback;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700339 private IBinder mToken;
Michael Wrightd86ecd22014-08-12 19:27:54 -0700340 private IBinder.DeathRecipient mDeathEater;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700341 private int mType;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700342
343 public MediaProjection(int type, int uid, String packageName) {
344 mType = type;
345 this.uid = uid;
346 this.packageName = packageName;
Michael Wrightd86ecd22014-08-12 19:27:54 -0700347 userHandle = new UserHandle(UserHandle.getUserId(uid));
Michael Wrightc39d47a2014-07-08 18:07:36 -0700348 }
349
350 @Override // Binder call
351 public boolean canProjectVideo() {
352 return mType == MediaProjectionManager.TYPE_MIRRORING ||
353 mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
354 }
355
356 @Override // Binder call
357 public boolean canProjectSecureVideo() {
358 return false;
359 }
360
361 @Override // Binder call
362 public boolean canProjectAudio() {
Kevin Rocard92488ea2019-02-21 11:01:25 -0800363 return mType == MediaProjectionManager.TYPE_MIRRORING
364 || mType == MediaProjectionManager.TYPE_PRESENTATION
365 || mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700366 }
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}