blob: 9e509f45392118af47ec302d715420c290fa10e5 [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;
Narayan Kamath396ce722019-03-01 16:53:26 +000020import android.app.ActivityManagerInternal;
Michael Wrightc39d47a2014-07-08 18:07:36 -070021import android.app.AppOpsManager;
Narayan Kamath396ce722019-03-01 16:53:26 +000022import android.app.IProcessObserver;
Michael Wrightc39d47a2014-07-08 18:07:36 -070023import android.content.Context;
Narayan Kamath396ce722019-03-01 16:53:26 +000024import android.content.pm.ApplicationInfo;
Michael Wrightc39d47a2014-07-08 18:07:36 -070025import android.content.pm.PackageManager;
Narayan Kamath396ce722019-03-01 16:53:26 +000026import android.content.pm.PackageManager.NameNotFoundException;
27import android.content.pm.ServiceInfo;
Michael Wrightc39d47a2014-07-08 18:07:36 -070028import android.hardware.display.DisplayManager;
Michael Wright89c2cb62014-09-14 13:06:34 -070029import android.media.MediaRouter;
Michael Wrightc39d47a2014-07-08 18:07:36 -070030import android.media.projection.IMediaProjection;
31import android.media.projection.IMediaProjectionCallback;
Kevin Rocard92488ea2019-02-21 11:01:25 -080032import android.media.projection.IMediaProjectionManager;
Michael Wrightd86ecd22014-08-12 19:27:54 -070033import android.media.projection.IMediaProjectionWatcherCallback;
34import android.media.projection.MediaProjectionInfo;
Michael Wrightc39d47a2014-07-08 18:07:36 -070035import android.media.projection.MediaProjectionManager;
36import android.os.Binder;
Narayan Kamath396ce722019-03-01 16:53:26 +000037import android.os.Build;
Michael Wrightc39d47a2014-07-08 18:07:36 -070038import android.os.Handler;
39import android.os.IBinder;
Michael Wrightc39d47a2014-07-08 18:07:36 -070040import android.os.Looper;
Michael Wrightc39d47a2014-07-08 18:07:36 -070041import android.os.RemoteException;
Michael Wrightd86ecd22014-08-12 19:27:54 -070042import android.os.UserHandle;
Michael Wrightc39d47a2014-07-08 18:07:36 -070043import android.util.ArrayMap;
44import android.util.Slog;
45
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060046import com.android.internal.util.DumpUtils;
Narayan Kamath396ce722019-03-01 16:53:26 +000047import com.android.server.LocalServices;
Michael Wrightc39d47a2014-07-08 18:07:36 -070048import com.android.server.SystemService;
Kevin Rocard92488ea2019-02-21 11:01:25 -080049import com.android.server.Watchdog;
Michael Wrightc39d47a2014-07-08 18:07:36 -070050
51import java.io.FileDescriptor;
52import java.io.PrintWriter;
Michael Wrightc39d47a2014-07-08 18:07:36 -070053import java.util.Map;
54
55/**
56 * Manages MediaProjection sessions.
57 *
58 * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
59 * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
60 * grants <b>must</b> validate the token before use by calling {@link
61 * IMediaProjectionService#isValidMediaProjection}.
62 */
63public final class MediaProjectionManagerService extends SystemService
64 implements Watchdog.Monitor {
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +010065 private static final boolean REQUIRE_FG_SERVICE_FOR_PROJECTION = true;
Michael Wrightc39d47a2014-07-08 18:07:36 -070066 private static final String TAG = "MediaProjectionManagerService";
67
68 private final Object mLock = new Object(); // Protects the list of media projections
Michael Wrightd86ecd22014-08-12 19:27:54 -070069 private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;
70 private final CallbackDelegate mCallbackDelegate;
Michael Wrightc39d47a2014-07-08 18:07:36 -070071
72 private final Context mContext;
73 private final AppOpsManager mAppOps;
Narayan Kamath396ce722019-03-01 16:53:26 +000074 private final ActivityManagerInternal mActivityManagerInternal;
75 private final PackageManager mPackageManager;
Michael Wrightc39d47a2014-07-08 18:07:36 -070076
Michael Wright89c2cb62014-09-14 13:06:34 -070077 private final MediaRouter mMediaRouter;
78 private final MediaRouterCallback mMediaRouterCallback;
79 private MediaRouter.RouteInfo mMediaRouteInfo;
80
Michael Wrightd86ecd22014-08-12 19:27:54 -070081 private IBinder mProjectionToken;
82 private MediaProjection mProjectionGrant;
83
Michael Wrightc39d47a2014-07-08 18:07:36 -070084 public MediaProjectionManagerService(Context context) {
85 super(context);
86 mContext = context;
Michael Wrightd86ecd22014-08-12 19:27:54 -070087 mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
88 mCallbackDelegate = new CallbackDelegate();
Michael Wrightc39d47a2014-07-08 18:07:36 -070089 mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
Narayan Kamath396ce722019-03-01 16:53:26 +000090 mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
91 mPackageManager = mContext.getPackageManager();
Michael Wright89c2cb62014-09-14 13:06:34 -070092 mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
93 mMediaRouterCallback = new MediaRouterCallback();
Michael Wrightc39d47a2014-07-08 18:07:36 -070094 Watchdog.getInstance().addMonitor(this);
95 }
96
97 @Override
98 public void onStart() {
99 publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
100 false /*allowIsolated*/);
Jason Monk4444c5b2014-10-27 19:20:02 -0400101 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
102 MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
Narayan Kamath13c60c32019-03-21 16:32:56 +0000103 if (REQUIRE_FG_SERVICE_FOR_PROJECTION) {
104 mActivityManagerInternal.registerProcessObserver(new IProcessObserver.Stub() {
105 @Override
106 public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
107 }
Narayan Kamath396ce722019-03-01 16:53:26 +0000108
Narayan Kamath13c60c32019-03-21 16:32:56 +0000109 @Override
110 public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
111 MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
112 serviceTypes);
113 }
Narayan Kamath396ce722019-03-01 16:53:26 +0000114
Narayan Kamath13c60c32019-03-21 16:32:56 +0000115 @Override
116 public void onProcessDied(int pid, int uid) {
117 }
118 });
119 }
Michael Wright89c2cb62014-09-14 13:06:34 -0700120 }
121
122 @Override
123 public void onSwitchUser(int userId) {
124 mMediaRouter.rebindAsUser(userId);
Michael Wright05aab582015-02-06 14:08:13 -0800125 synchronized (mLock) {
126 if (mProjectionGrant != null) {
127 mProjectionGrant.stop();
128 }
129 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700130 }
131
132 @Override
133 public void monitor() {
134 synchronized (mLock) { /* check for deadlock */ }
135 }
136
Narayan Kamath396ce722019-03-01 16:53:26 +0000137 /**
138 * Called when the set of active foreground service types for a given {@code uid / pid} changes.
139 * We will stop the active projection grant if its owner targets {@code Q} or higher and has no
140 * started foreground services of type {@code FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
141 */
142 private void handleForegroundServicesChanged(int pid, int uid, int serviceTypes) {
143 synchronized (mLock) {
144 if (mProjectionGrant == null || mProjectionGrant.uid != uid) {
145 return;
146 }
147
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100148 if (!mProjectionGrant.requiresForegroundService()) {
Narayan Kamath396ce722019-03-01 16:53:26 +0000149 return;
150 }
151
152 if ((serviceTypes & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION) != 0) {
153 return;
154 }
155
156 mProjectionGrant.stop();
157 }
158 }
159
Michael Wrightd86ecd22014-08-12 19:27:54 -0700160 private void startProjectionLocked(final MediaProjection projection) {
161 if (mProjectionGrant != null) {
162 mProjectionGrant.stop();
163 }
Michael Wright89c2cb62014-09-14 13:06:34 -0700164 if (mMediaRouteInfo != null) {
Sungsoo Lim59579ce2017-07-14 10:28:51 -0700165 mMediaRouter.getFallbackRoute().select();
Michael Wright89c2cb62014-09-14 13:06:34 -0700166 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700167 mProjectionToken = projection.asBinder();
168 mProjectionGrant = projection;
169 dispatchStart(projection);
170 }
171
172 private void stopProjectionLocked(final MediaProjection projection) {
173 mProjectionToken = null;
174 mProjectionGrant = null;
175 dispatchStop(projection);
176 }
177
178 private void addCallback(final IMediaProjectionWatcherCallback callback) {
179 IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
180 @Override
181 public void binderDied() {
Jae Seoac3f8e52015-08-04 11:12:13 -0700182 removeCallback(callback);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700183 }
184 };
185 synchronized (mLock) {
186 mCallbackDelegate.add(callback);
187 linkDeathRecipientLocked(callback, deathRecipient);
188 }
189 }
190
191 private void removeCallback(IMediaProjectionWatcherCallback callback) {
192 synchronized (mLock) {
193 unlinkDeathRecipientLocked(callback);
John Spurlock78b8c8f2014-08-25 17:52:06 -0400194 mCallbackDelegate.remove(callback);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700195 }
196 }
197
198 private void linkDeathRecipientLocked(IMediaProjectionWatcherCallback callback,
199 IBinder.DeathRecipient deathRecipient) {
200 try {
201 final IBinder token = callback.asBinder();
202 token.linkToDeath(deathRecipient, 0);
203 mDeathEaters.put(token, deathRecipient);
204 } catch (RemoteException e) {
205 Slog.e(TAG, "Unable to link to death for media projection monitoring callback", e);
206 }
207 }
208
209 private void unlinkDeathRecipientLocked(IMediaProjectionWatcherCallback callback) {
210 final IBinder token = callback.asBinder();
211 IBinder.DeathRecipient deathRecipient = mDeathEaters.remove(token);
212 if (deathRecipient != null) {
213 token.unlinkToDeath(deathRecipient, 0);
214 }
215 }
216
217 private void dispatchStart(MediaProjection projection) {
218 mCallbackDelegate.dispatchStart(projection);
219 }
220
221 private void dispatchStop(MediaProjection projection) {
222 mCallbackDelegate.dispatchStop(projection);
223 }
224
225 private boolean isValidMediaProjection(IBinder token) {
226 synchronized (mLock) {
227 if (mProjectionToken != null) {
228 return mProjectionToken.equals(token);
229 }
230 return false;
231 }
232 }
233
234 private MediaProjectionInfo getActiveProjectionInfo() {
235 synchronized (mLock) {
236 if (mProjectionGrant == null) {
237 return null;
238 }
239 return mProjectionGrant.getProjectionInfo();
240 }
241 }
242
Michael Wrightc39d47a2014-07-08 18:07:36 -0700243 private void dump(final PrintWriter pw) {
244 pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
245 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700246 pw.println("Media Projection: ");
247 if (mProjectionGrant != null ) {
248 mProjectionGrant.dump(pw);
249 } else {
250 pw.println("null");
Michael Wrightc39d47a2014-07-08 18:07:36 -0700251 }
252 }
253 }
254
255 private final class BinderService extends IMediaProjectionManager.Stub {
256
257 @Override // Binder call
258 public boolean hasProjectionPermission(int uid, String packageName) {
259 long token = Binder.clearCallingIdentity();
260 boolean hasPermission = false;
261 try {
262 hasPermission |= checkPermission(packageName,
263 android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
Michael Wright6720be42014-07-29 19:14:16 -0700264 || mAppOps.noteOpNoThrow(
Michael Wrightc39d47a2014-07-08 18:07:36 -0700265 AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
266 == AppOpsManager.MODE_ALLOWED;
267 } finally {
268 Binder.restoreCallingIdentity(token);
269 }
270 return hasPermission;
271 }
272
273 @Override // Binder call
274 public IMediaProjection createProjection(int uid, String packageName, int type,
275 boolean isPermanentGrant) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700276 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
Michael Wrightc39d47a2014-07-08 18:07:36 -0700277 != PackageManager.PERMISSION_GRANTED) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700278 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
Michael Wrightc39d47a2014-07-08 18:07:36 -0700279 + "projection permission");
280 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700281 if (packageName == null || packageName.isEmpty()) {
282 throw new IllegalArgumentException("package name must not be empty");
283 }
Narayan Kamath396ce722019-03-01 16:53:26 +0000284
Narayan Kamathe6d63d72019-05-08 14:45:32 +0100285 final UserHandle callingUser = Binder.getCallingUserHandle();
Michael Wrightc39d47a2014-07-08 18:07:36 -0700286 long callingToken = Binder.clearCallingIdentity();
Narayan Kamath396ce722019-03-01 16:53:26 +0000287
Michael Wrightc39d47a2014-07-08 18:07:36 -0700288 MediaProjection projection;
289 try {
Narayan Kamath396ce722019-03-01 16:53:26 +0000290 ApplicationInfo ai;
291 try {
Narayan Kamathe6d63d72019-05-08 14:45:32 +0100292 ai = mPackageManager.getApplicationInfoAsUser(packageName, 0, callingUser);
Narayan Kamath396ce722019-03-01 16:53:26 +0000293 } catch (NameNotFoundException e) {
294 throw new IllegalArgumentException("No package matching :" + packageName);
295 }
296
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100297 projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
298 ai.isPrivilegedApp());
Michael Wrightc39d47a2014-07-08 18:07:36 -0700299 if (isPermanentGrant) {
300 mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
301 projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
302 }
303 } finally {
304 Binder.restoreCallingIdentity(callingToken);
305 }
306 return projection;
307 }
308
309 @Override // Binder call
310 public boolean isValidMediaProjection(IMediaProjection projection) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700311 return MediaProjectionManagerService.this.isValidMediaProjection(
312 projection.asBinder());
313 }
314
315 @Override // Binder call
316 public MediaProjectionInfo getActiveProjectionInfo() {
317 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
318 != PackageManager.PERMISSION_GRANTED) {
319 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
320 + "projection callbacks");
321 }
322 final long token = Binder.clearCallingIdentity();
323 try {
324 return MediaProjectionManagerService.this.getActiveProjectionInfo();
325 } finally {
326 Binder.restoreCallingIdentity(token);
327 }
328 }
329
330 @Override // Binder call
331 public void stopActiveProjection() {
332 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
333 != PackageManager.PERMISSION_GRANTED) {
334 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
335 + "projection callbacks");
336 }
337 final long token = Binder.clearCallingIdentity();
338 try {
339 if (mProjectionGrant != null) {
340 mProjectionGrant.stop();
341 }
342 } finally {
343 Binder.restoreCallingIdentity(token);
344 }
345
346 }
347
348 @Override //Binder call
349 public void addCallback(final IMediaProjectionWatcherCallback callback) {
350 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
351 != PackageManager.PERMISSION_GRANTED) {
352 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
353 + "projection callbacks");
354 }
355 final long token = Binder.clearCallingIdentity();
356 try {
357 MediaProjectionManagerService.this.addCallback(callback);
358 } finally {
359 Binder.restoreCallingIdentity(token);
360 }
361 }
362
363 @Override
364 public void removeCallback(IMediaProjectionWatcherCallback callback) {
365 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
366 != PackageManager.PERMISSION_GRANTED) {
367 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove "
368 + "projection callbacks");
369 }
370 final long token = Binder.clearCallingIdentity();
371 try {
372 MediaProjectionManagerService.this.removeCallback(callback);
373 } finally {
374 Binder.restoreCallingIdentity(token);
375 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700376 }
377
378 @Override // Binder call
379 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600380 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700381 final long token = Binder.clearCallingIdentity();
382 try {
John Spurlock9b843092014-10-20 13:37:48 -0400383 MediaProjectionManagerService.this.dump(pw);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700384 } finally {
385 Binder.restoreCallingIdentity(token);
386 }
387 }
388
Michael Wrightd86ecd22014-08-12 19:27:54 -0700389
Michael Wrightc39d47a2014-07-08 18:07:36 -0700390 private boolean checkPermission(String packageName, String permission) {
391 return mContext.getPackageManager().checkPermission(permission, packageName)
392 == PackageManager.PERMISSION_GRANTED;
393 }
394 }
395
Michael Wrightd86ecd22014-08-12 19:27:54 -0700396 private final class MediaProjection extends IMediaProjection.Stub {
397 public final int uid;
398 public final String packageName;
399 public final UserHandle userHandle;
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100400 private final int mTargetSdkVersion;
401 private final boolean mIsPrivileged;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700402
Jae Seoac3f8e52015-08-04 11:12:13 -0700403 private IMediaProjectionCallback mCallback;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700404 private IBinder mToken;
Michael Wrightd86ecd22014-08-12 19:27:54 -0700405 private IBinder.DeathRecipient mDeathEater;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700406 private int mType;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700407
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100408 MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
409 boolean isPrivileged) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700410 mType = type;
411 this.uid = uid;
412 this.packageName = packageName;
Michael Wrightd86ecd22014-08-12 19:27:54 -0700413 userHandle = new UserHandle(UserHandle.getUserId(uid));
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100414 mTargetSdkVersion = targetSdkVersion;
415 mIsPrivileged = isPrivileged;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700416 }
417
418 @Override // Binder call
419 public boolean canProjectVideo() {
420 return mType == MediaProjectionManager.TYPE_MIRRORING ||
421 mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
422 }
423
424 @Override // Binder call
425 public boolean canProjectSecureVideo() {
426 return false;
427 }
428
429 @Override // Binder call
430 public boolean canProjectAudio() {
Kevin Rocard92488ea2019-02-21 11:01:25 -0800431 return mType == MediaProjectionManager.TYPE_MIRRORING
432 || mType == MediaProjectionManager.TYPE_PRESENTATION
433 || mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700434 }
435
436 @Override // Binder call
Michael Wright6720be42014-07-29 19:14:16 -0700437 public int applyVirtualDisplayFlags(int flags) {
438 if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
439 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
440 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
441 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
442 return flags;
443 } else if (mType == MediaProjectionManager.TYPE_MIRRORING) {
444 flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
445 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
446 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
447 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
448 return flags;
449 } else if (mType == MediaProjectionManager.TYPE_PRESENTATION) {
450 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
451 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
452 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
453 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
454 return flags;
455 } else {
456 throw new RuntimeException("Unknown MediaProjection type");
Michael Wrightc39d47a2014-07-08 18:07:36 -0700457 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700458 }
459
460 @Override // Binder call
Michael Wrightd86ecd22014-08-12 19:27:54 -0700461 public void start(final IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700462 if (callback == null) {
463 throw new IllegalArgumentException("callback must not be null");
464 }
465 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700466 if (isValidMediaProjection(asBinder())) {
Zimuzoef531e02018-11-01 16:04:08 +0000467 Slog.w(TAG, "UID " + Binder.getCallingUid()
468 + " attempted to start already started MediaProjection");
469 return;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700470 }
Narayan Kamath396ce722019-03-01 16:53:26 +0000471
Narayan Kamath13c60c32019-03-21 16:32:56 +0000472 if (REQUIRE_FG_SERVICE_FOR_PROJECTION
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100473 && requiresForegroundService()
Narayan Kamath396ce722019-03-01 16:53:26 +0000474 && !mActivityManagerInternal.hasRunningForegroundService(
475 uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)) {
476 throw new SecurityException("Media projections require a foreground service"
477 + " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION");
478 }
479
Jae Seoac3f8e52015-08-04 11:12:13 -0700480 mCallback = callback;
481 registerCallback(mCallback);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700482 try {
483 mToken = callback.asBinder();
Michael Wrightd86ecd22014-08-12 19:27:54 -0700484 mDeathEater = new IBinder.DeathRecipient() {
485 @Override
486 public void binderDied() {
487 mCallbackDelegate.remove(callback);
488 stop();
489 }
490 };
491 mToken.linkToDeath(mDeathEater, 0);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700492 } catch (RemoteException e) {
493 Slog.w(TAG,
494 "MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
495 return;
496 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700497 startProjectionLocked(this);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700498 }
499 }
500
501 @Override // Binder call
502 public void stop() {
503 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700504 if (!isValidMediaProjection(asBinder())) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700505 Slog.w(TAG, "Attempted to stop inactive MediaProjection "
506 + "(uid=" + Binder.getCallingUid() + ", "
507 + "pid=" + Binder.getCallingPid() + ")");
508 return;
509 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700510 stopProjectionLocked(this);
Jae Seoac3f8e52015-08-04 11:12:13 -0700511 mToken.unlinkToDeath(mDeathEater, 0);
512 mToken = null;
513 unregisterCallback(mCallback);
514 mCallback = null;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700515 }
516 }
517
518 @Override
Michael Wrightcde5bb42014-09-08 13:26:34 -0700519 public void registerCallback(IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700520 if (callback == null) {
521 throw new IllegalArgumentException("callback must not be null");
522 }
523 mCallbackDelegate.add(callback);
524 }
525
526 @Override
Michael Wrightcde5bb42014-09-08 13:26:34 -0700527 public void unregisterCallback(IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700528 if (callback == null) {
529 throw new IllegalArgumentException("callback must not be null");
530 }
531 mCallbackDelegate.remove(callback);
532 }
533
Michael Wrightd86ecd22014-08-12 19:27:54 -0700534 public MediaProjectionInfo getProjectionInfo() {
535 return new MediaProjectionInfo(packageName, userHandle);
536 }
537
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100538 boolean requiresForegroundService() {
539 return mTargetSdkVersion >= Build.VERSION_CODES.Q && !mIsPrivileged;
540 }
541
Michael Wrightd86ecd22014-08-12 19:27:54 -0700542 public void dump(PrintWriter pw) {
543 pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
Michael Wrightc39d47a2014-07-08 18:07:36 -0700544 }
545 }
546
Michael Wright89c2cb62014-09-14 13:06:34 -0700547 private class MediaRouterCallback extends MediaRouter.SimpleCallback {
548 @Override
549 public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) {
550 synchronized (mLock) {
551 if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
552 mMediaRouteInfo = info;
553 if (mProjectionGrant != null) {
554 mProjectionGrant.stop();
555 }
556 }
557 }
558 }
559
560 @Override
561 public void onRouteUnselected(MediaRouter route, int type, MediaRouter.RouteInfo info) {
562 if (mMediaRouteInfo == info) {
563 mMediaRouteInfo = null;
564 }
565 }
566 }
567
Michael Wrightd86ecd22014-08-12 19:27:54 -0700568
Michael Wrightc39d47a2014-07-08 18:07:36 -0700569 private static class CallbackDelegate {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700570 private Map<IBinder, IMediaProjectionCallback> mClientCallbacks;
571 private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700572 private Handler mHandler;
573 private Object mLock = new Object();
574
575 public CallbackDelegate() {
576 mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700577 mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
578 mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
Michael Wrightc39d47a2014-07-08 18:07:36 -0700579 }
580
581 public void add(IMediaProjectionCallback callback) {
582 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700583 mClientCallbacks.put(callback.asBinder(), callback);
584 }
585 }
586
587 public void add(IMediaProjectionWatcherCallback callback) {
588 synchronized (mLock) {
589 mWatcherCallbacks.put(callback.asBinder(), callback);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700590 }
591 }
592
593 public void remove(IMediaProjectionCallback callback) {
594 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700595 mClientCallbacks.remove(callback.asBinder());
Michael Wrightc39d47a2014-07-08 18:07:36 -0700596 }
597 }
598
Michael Wrightd86ecd22014-08-12 19:27:54 -0700599 public void remove(IMediaProjectionWatcherCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700600 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700601 mWatcherCallbacks.remove(callback.asBinder());
602 }
603 }
604
605 public void dispatchStart(MediaProjection projection) {
606 if (projection == null) {
607 Slog.e(TAG, "Tried to dispatch start notification for a null media projection."
608 + " Ignoring!");
609 return;
610 }
611 synchronized (mLock) {
612 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
613 MediaProjectionInfo info = projection.getProjectionInfo();
614 mHandler.post(new WatcherStartCallback(info, callback));
615 }
616 }
617 }
618
619 public void dispatchStop(MediaProjection projection) {
620 if (projection == null) {
621 Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
622 + " Ignoring!");
623 return;
624 }
625 synchronized (mLock) {
626 for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
627 mHandler.post(new ClientStopCallback(callback));
628 }
629
630 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
631 MediaProjectionInfo info = projection.getProjectionInfo();
632 mHandler.post(new WatcherStopCallback(info, callback));
Michael Wrightc39d47a2014-07-08 18:07:36 -0700633 }
634 }
635 }
636 }
637
Michael Wrightd86ecd22014-08-12 19:27:54 -0700638 private static final class WatcherStartCallback implements Runnable {
639 private IMediaProjectionWatcherCallback mCallback;
640 private MediaProjectionInfo mInfo;
641
642 public WatcherStartCallback(MediaProjectionInfo info,
643 IMediaProjectionWatcherCallback callback) {
644 mInfo = info;
645 mCallback = callback;
646 }
647
648 @Override
649 public void run() {
650 try {
651 mCallback.onStart(mInfo);
652 } catch (RemoteException e) {
653 Slog.w(TAG, "Failed to notify media projection has stopped", e);
654 }
655 }
656 }
657
658 private static final class WatcherStopCallback implements Runnable {
659 private IMediaProjectionWatcherCallback mCallback;
660 private MediaProjectionInfo mInfo;
661
662 public WatcherStopCallback(MediaProjectionInfo info,
663 IMediaProjectionWatcherCallback callback) {
664 mInfo = info;
665 mCallback = callback;
666 }
667
668 @Override
669 public void run() {
670 try {
671 mCallback.onStop(mInfo);
672 } catch (RemoteException e) {
673 Slog.w(TAG, "Failed to notify media projection has stopped", e);
674 }
675 }
676 }
677
678 private static final class ClientStopCallback implements Runnable {
679 private IMediaProjectionCallback mCallback;
680
681 public ClientStopCallback(IMediaProjectionCallback callback) {
682 mCallback = callback;
683 }
684
685 @Override
686 public void run() {
687 try {
688 mCallback.onStop();
689 } catch (RemoteException e) {
690 Slog.w(TAG, "Failed to notify media projection has stopped", e);
691 }
692 }
693 }
694
695
Michael Wrightc39d47a2014-07-08 18:07:36 -0700696 private static String typeToString(int type) {
697 switch (type) {
698 case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
699 return "TYPE_SCREEN_CAPTURE";
700 case MediaProjectionManager.TYPE_MIRRORING:
701 return "TYPE_MIRRORING";
702 case MediaProjectionManager.TYPE_PRESENTATION:
703 return "TYPE_PRESENTATION";
704 }
705 return Integer.toString(type);
706 }
707}