blob: 1a749b34d85eb43b17b7b670bef24786cbb18b29 [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;
Alan Stokesb3d4b642020-04-24 17:08:26 +010025import android.content.pm.PackageInfo;
Michael Wrightc39d47a2014-07-08 18:07:36 -070026import android.content.pm.PackageManager;
Narayan Kamath396ce722019-03-01 16:53:26 +000027import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.pm.ServiceInfo;
Michael Wrightc39d47a2014-07-08 18:07:36 -070029import android.hardware.display.DisplayManager;
Michael Wright89c2cb62014-09-14 13:06:34 -070030import android.media.MediaRouter;
Michael Wrightc39d47a2014-07-08 18:07:36 -070031import android.media.projection.IMediaProjection;
32import android.media.projection.IMediaProjectionCallback;
Kevin Rocard92488ea2019-02-21 11:01:25 -080033import android.media.projection.IMediaProjectionManager;
Michael Wrightd86ecd22014-08-12 19:27:54 -070034import android.media.projection.IMediaProjectionWatcherCallback;
35import android.media.projection.MediaProjectionInfo;
Michael Wrightc39d47a2014-07-08 18:07:36 -070036import android.media.projection.MediaProjectionManager;
37import android.os.Binder;
Narayan Kamath396ce722019-03-01 16:53:26 +000038import android.os.Build;
Michael Wrightc39d47a2014-07-08 18:07:36 -070039import android.os.Handler;
40import android.os.IBinder;
Michael Wrightc39d47a2014-07-08 18:07:36 -070041import android.os.Looper;
Michael Wrightc39d47a2014-07-08 18:07:36 -070042import android.os.RemoteException;
Michael Wrightd86ecd22014-08-12 19:27:54 -070043import android.os.UserHandle;
Michael Wrightc39d47a2014-07-08 18:07:36 -070044import android.util.ArrayMap;
45import android.util.Slog;
46
Alan Stokesb3d4b642020-04-24 17:08:26 +010047import com.android.internal.util.ArrayUtils;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060048import com.android.internal.util.DumpUtils;
Narayan Kamath396ce722019-03-01 16:53:26 +000049import com.android.server.LocalServices;
Michael Wrightc39d47a2014-07-08 18:07:36 -070050import com.android.server.SystemService;
Kevin Rocard92488ea2019-02-21 11:01:25 -080051import com.android.server.Watchdog;
Michael Wrightc39d47a2014-07-08 18:07:36 -070052
53import java.io.FileDescriptor;
54import java.io.PrintWriter;
Michael Wrightc39d47a2014-07-08 18:07:36 -070055import java.util.Map;
56
57/**
58 * Manages MediaProjection sessions.
59 *
60 * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
61 * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
62 * grants <b>must</b> validate the token before use by calling {@link
63 * IMediaProjectionService#isValidMediaProjection}.
64 */
65public final class MediaProjectionManagerService extends SystemService
66 implements Watchdog.Monitor {
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +010067 private static final boolean REQUIRE_FG_SERVICE_FOR_PROJECTION = true;
Michael Wrightc39d47a2014-07-08 18:07:36 -070068 private static final String TAG = "MediaProjectionManagerService";
69
70 private final Object mLock = new Object(); // Protects the list of media projections
Michael Wrightd86ecd22014-08-12 19:27:54 -070071 private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;
72 private final CallbackDelegate mCallbackDelegate;
Michael Wrightc39d47a2014-07-08 18:07:36 -070073
74 private final Context mContext;
75 private final AppOpsManager mAppOps;
Narayan Kamath396ce722019-03-01 16:53:26 +000076 private final ActivityManagerInternal mActivityManagerInternal;
77 private final PackageManager mPackageManager;
Michael Wrightc39d47a2014-07-08 18:07:36 -070078
Michael Wright89c2cb62014-09-14 13:06:34 -070079 private final MediaRouter mMediaRouter;
80 private final MediaRouterCallback mMediaRouterCallback;
81 private MediaRouter.RouteInfo mMediaRouteInfo;
82
Michael Wrightd86ecd22014-08-12 19:27:54 -070083 private IBinder mProjectionToken;
84 private MediaProjection mProjectionGrant;
85
Michael Wrightc39d47a2014-07-08 18:07:36 -070086 public MediaProjectionManagerService(Context context) {
87 super(context);
88 mContext = context;
Michael Wrightd86ecd22014-08-12 19:27:54 -070089 mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
90 mCallbackDelegate = new CallbackDelegate();
Michael Wrightc39d47a2014-07-08 18:07:36 -070091 mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
Narayan Kamath396ce722019-03-01 16:53:26 +000092 mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
93 mPackageManager = mContext.getPackageManager();
Michael Wright89c2cb62014-09-14 13:06:34 -070094 mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
95 mMediaRouterCallback = new MediaRouterCallback();
Michael Wrightc39d47a2014-07-08 18:07:36 -070096 Watchdog.getInstance().addMonitor(this);
97 }
98
99 @Override
100 public void onStart() {
101 publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
102 false /*allowIsolated*/);
Jason Monk4444c5b2014-10-27 19:20:02 -0400103 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
104 MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
Narayan Kamath13c60c32019-03-21 16:32:56 +0000105 if (REQUIRE_FG_SERVICE_FOR_PROJECTION) {
106 mActivityManagerInternal.registerProcessObserver(new IProcessObserver.Stub() {
107 @Override
108 public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
109 }
Narayan Kamath396ce722019-03-01 16:53:26 +0000110
Narayan Kamath13c60c32019-03-21 16:32:56 +0000111 @Override
112 public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
113 MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
114 serviceTypes);
115 }
Narayan Kamath396ce722019-03-01 16:53:26 +0000116
Narayan Kamath13c60c32019-03-21 16:32:56 +0000117 @Override
118 public void onProcessDied(int pid, int uid) {
119 }
120 });
121 }
Michael Wright89c2cb62014-09-14 13:06:34 -0700122 }
123
124 @Override
125 public void onSwitchUser(int userId) {
126 mMediaRouter.rebindAsUser(userId);
Michael Wright05aab582015-02-06 14:08:13 -0800127 synchronized (mLock) {
128 if (mProjectionGrant != null) {
129 mProjectionGrant.stop();
130 }
131 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700132 }
133
134 @Override
135 public void monitor() {
136 synchronized (mLock) { /* check for deadlock */ }
137 }
138
Narayan Kamath396ce722019-03-01 16:53:26 +0000139 /**
140 * Called when the set of active foreground service types for a given {@code uid / pid} changes.
141 * We will stop the active projection grant if its owner targets {@code Q} or higher and has no
142 * started foreground services of type {@code FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
143 */
144 private void handleForegroundServicesChanged(int pid, int uid, int serviceTypes) {
145 synchronized (mLock) {
146 if (mProjectionGrant == null || mProjectionGrant.uid != uid) {
147 return;
148 }
149
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100150 if (!mProjectionGrant.requiresForegroundService()) {
Narayan Kamath396ce722019-03-01 16:53:26 +0000151 return;
152 }
153
154 if ((serviceTypes & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION) != 0) {
155 return;
156 }
157
158 mProjectionGrant.stop();
159 }
160 }
161
Michael Wrightd86ecd22014-08-12 19:27:54 -0700162 private void startProjectionLocked(final MediaProjection projection) {
163 if (mProjectionGrant != null) {
164 mProjectionGrant.stop();
165 }
Michael Wright89c2cb62014-09-14 13:06:34 -0700166 if (mMediaRouteInfo != null) {
Sungsoo Lim59579ce2017-07-14 10:28:51 -0700167 mMediaRouter.getFallbackRoute().select();
Michael Wright89c2cb62014-09-14 13:06:34 -0700168 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700169 mProjectionToken = projection.asBinder();
170 mProjectionGrant = projection;
171 dispatchStart(projection);
172 }
173
174 private void stopProjectionLocked(final MediaProjection projection) {
175 mProjectionToken = null;
176 mProjectionGrant = null;
177 dispatchStop(projection);
178 }
179
180 private void addCallback(final IMediaProjectionWatcherCallback callback) {
181 IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
182 @Override
183 public void binderDied() {
Jae Seoac3f8e52015-08-04 11:12:13 -0700184 removeCallback(callback);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700185 }
186 };
187 synchronized (mLock) {
188 mCallbackDelegate.add(callback);
189 linkDeathRecipientLocked(callback, deathRecipient);
190 }
191 }
192
193 private void removeCallback(IMediaProjectionWatcherCallback callback) {
194 synchronized (mLock) {
195 unlinkDeathRecipientLocked(callback);
John Spurlock78b8c8f2014-08-25 17:52:06 -0400196 mCallbackDelegate.remove(callback);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700197 }
198 }
199
200 private void linkDeathRecipientLocked(IMediaProjectionWatcherCallback callback,
201 IBinder.DeathRecipient deathRecipient) {
202 try {
203 final IBinder token = callback.asBinder();
204 token.linkToDeath(deathRecipient, 0);
205 mDeathEaters.put(token, deathRecipient);
206 } catch (RemoteException e) {
207 Slog.e(TAG, "Unable to link to death for media projection monitoring callback", e);
208 }
209 }
210
211 private void unlinkDeathRecipientLocked(IMediaProjectionWatcherCallback callback) {
212 final IBinder token = callback.asBinder();
213 IBinder.DeathRecipient deathRecipient = mDeathEaters.remove(token);
214 if (deathRecipient != null) {
215 token.unlinkToDeath(deathRecipient, 0);
216 }
217 }
218
219 private void dispatchStart(MediaProjection projection) {
220 mCallbackDelegate.dispatchStart(projection);
221 }
222
223 private void dispatchStop(MediaProjection projection) {
224 mCallbackDelegate.dispatchStop(projection);
225 }
226
227 private boolean isValidMediaProjection(IBinder token) {
228 synchronized (mLock) {
229 if (mProjectionToken != null) {
230 return mProjectionToken.equals(token);
231 }
232 return false;
233 }
234 }
235
236 private MediaProjectionInfo getActiveProjectionInfo() {
237 synchronized (mLock) {
238 if (mProjectionGrant == null) {
239 return null;
240 }
241 return mProjectionGrant.getProjectionInfo();
242 }
243 }
244
Michael Wrightc39d47a2014-07-08 18:07:36 -0700245 private void dump(final PrintWriter pw) {
246 pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
247 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700248 pw.println("Media Projection: ");
249 if (mProjectionGrant != null ) {
250 mProjectionGrant.dump(pw);
251 } else {
252 pw.println("null");
Michael Wrightc39d47a2014-07-08 18:07:36 -0700253 }
254 }
255 }
256
257 private final class BinderService extends IMediaProjectionManager.Stub {
258
259 @Override // Binder call
260 public boolean hasProjectionPermission(int uid, String packageName) {
261 long token = Binder.clearCallingIdentity();
262 boolean hasPermission = false;
263 try {
264 hasPermission |= checkPermission(packageName,
265 android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
Michael Wright6720be42014-07-29 19:14:16 -0700266 || mAppOps.noteOpNoThrow(
Michael Wrightc39d47a2014-07-08 18:07:36 -0700267 AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
268 == AppOpsManager.MODE_ALLOWED;
269 } finally {
270 Binder.restoreCallingIdentity(token);
271 }
272 return hasPermission;
273 }
274
275 @Override // Binder call
276 public IMediaProjection createProjection(int uid, String packageName, int type,
277 boolean isPermanentGrant) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700278 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
Michael Wrightc39d47a2014-07-08 18:07:36 -0700279 != PackageManager.PERMISSION_GRANTED) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700280 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
Michael Wrightc39d47a2014-07-08 18:07:36 -0700281 + "projection permission");
282 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700283 if (packageName == null || packageName.isEmpty()) {
284 throw new IllegalArgumentException("package name must not be empty");
285 }
Narayan Kamath396ce722019-03-01 16:53:26 +0000286
Narayan Kamathe6d63d72019-05-08 14:45:32 +0100287 final UserHandle callingUser = Binder.getCallingUserHandle();
Michael Wrightc39d47a2014-07-08 18:07:36 -0700288 long callingToken = Binder.clearCallingIdentity();
Narayan Kamath396ce722019-03-01 16:53:26 +0000289
Michael Wrightc39d47a2014-07-08 18:07:36 -0700290 MediaProjection projection;
291 try {
Narayan Kamath396ce722019-03-01 16:53:26 +0000292 ApplicationInfo ai;
293 try {
Narayan Kamathe6d63d72019-05-08 14:45:32 +0100294 ai = mPackageManager.getApplicationInfoAsUser(packageName, 0, callingUser);
Narayan Kamath396ce722019-03-01 16:53:26 +0000295 } catch (NameNotFoundException e) {
296 throw new IllegalArgumentException("No package matching :" + packageName);
297 }
298
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100299 projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
300 ai.isPrivilegedApp());
Michael Wrightc39d47a2014-07-08 18:07:36 -0700301 if (isPermanentGrant) {
302 mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
303 projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
304 }
305 } finally {
306 Binder.restoreCallingIdentity(callingToken);
307 }
308 return projection;
309 }
310
311 @Override // Binder call
312 public boolean isValidMediaProjection(IMediaProjection projection) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700313 return MediaProjectionManagerService.this.isValidMediaProjection(
314 projection.asBinder());
315 }
316
317 @Override // Binder call
318 public MediaProjectionInfo getActiveProjectionInfo() {
319 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
320 != PackageManager.PERMISSION_GRANTED) {
321 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
322 + "projection callbacks");
323 }
324 final long token = Binder.clearCallingIdentity();
325 try {
326 return MediaProjectionManagerService.this.getActiveProjectionInfo();
327 } finally {
328 Binder.restoreCallingIdentity(token);
329 }
330 }
331
332 @Override // Binder call
333 public void stopActiveProjection() {
334 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
335 != PackageManager.PERMISSION_GRANTED) {
336 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
337 + "projection callbacks");
338 }
339 final long token = Binder.clearCallingIdentity();
340 try {
341 if (mProjectionGrant != null) {
342 mProjectionGrant.stop();
343 }
344 } finally {
345 Binder.restoreCallingIdentity(token);
346 }
347
348 }
349
350 @Override //Binder call
351 public void addCallback(final IMediaProjectionWatcherCallback callback) {
352 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
353 != PackageManager.PERMISSION_GRANTED) {
354 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
355 + "projection callbacks");
356 }
357 final long token = Binder.clearCallingIdentity();
358 try {
359 MediaProjectionManagerService.this.addCallback(callback);
360 } finally {
361 Binder.restoreCallingIdentity(token);
362 }
363 }
364
365 @Override
366 public void removeCallback(IMediaProjectionWatcherCallback callback) {
367 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
368 != PackageManager.PERMISSION_GRANTED) {
369 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove "
370 + "projection callbacks");
371 }
372 final long token = Binder.clearCallingIdentity();
373 try {
374 MediaProjectionManagerService.this.removeCallback(callback);
375 } finally {
376 Binder.restoreCallingIdentity(token);
377 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700378 }
379
380 @Override // Binder call
381 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600382 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700383 final long token = Binder.clearCallingIdentity();
384 try {
John Spurlock9b843092014-10-20 13:37:48 -0400385 MediaProjectionManagerService.this.dump(pw);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700386 } finally {
387 Binder.restoreCallingIdentity(token);
388 }
389 }
390
Michael Wrightd86ecd22014-08-12 19:27:54 -0700391
Michael Wrightc39d47a2014-07-08 18:07:36 -0700392 private boolean checkPermission(String packageName, String permission) {
393 return mContext.getPackageManager().checkPermission(permission, packageName)
394 == PackageManager.PERMISSION_GRANTED;
395 }
396 }
397
Michael Wrightd86ecd22014-08-12 19:27:54 -0700398 private final class MediaProjection extends IMediaProjection.Stub {
399 public final int uid;
400 public final String packageName;
401 public final UserHandle userHandle;
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100402 private final int mTargetSdkVersion;
403 private final boolean mIsPrivileged;
Alan Stokesb3d4b642020-04-24 17:08:26 +0100404 private final int mType;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700405
Jae Seoac3f8e52015-08-04 11:12:13 -0700406 private IMediaProjectionCallback mCallback;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700407 private IBinder mToken;
Michael Wrightd86ecd22014-08-12 19:27:54 -0700408 private IBinder.DeathRecipient mDeathEater;
Alan Stokesb3d4b642020-04-24 17:08:26 +0100409 private boolean mRestoreSystemAlertWindow;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700410
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100411 MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
412 boolean isPrivileged) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700413 mType = type;
414 this.uid = uid;
415 this.packageName = packageName;
Michael Wrightd86ecd22014-08-12 19:27:54 -0700416 userHandle = new UserHandle(UserHandle.getUserId(uid));
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100417 mTargetSdkVersion = targetSdkVersion;
418 mIsPrivileged = isPrivileged;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700419 }
420
421 @Override // Binder call
422 public boolean canProjectVideo() {
423 return mType == MediaProjectionManager.TYPE_MIRRORING ||
424 mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
425 }
426
427 @Override // Binder call
428 public boolean canProjectSecureVideo() {
429 return false;
430 }
431
432 @Override // Binder call
433 public boolean canProjectAudio() {
Kevin Rocard92488ea2019-02-21 11:01:25 -0800434 return mType == MediaProjectionManager.TYPE_MIRRORING
435 || mType == MediaProjectionManager.TYPE_PRESENTATION
436 || mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700437 }
438
439 @Override // Binder call
Michael Wright6720be42014-07-29 19:14:16 -0700440 public int applyVirtualDisplayFlags(int flags) {
441 if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
442 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
443 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
444 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
445 return flags;
446 } else if (mType == MediaProjectionManager.TYPE_MIRRORING) {
447 flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
448 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
449 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
450 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
451 return flags;
452 } else if (mType == MediaProjectionManager.TYPE_PRESENTATION) {
453 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
454 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
455 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
456 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
457 return flags;
458 } else {
459 throw new RuntimeException("Unknown MediaProjection type");
Michael Wrightc39d47a2014-07-08 18:07:36 -0700460 }
Michael Wrightc39d47a2014-07-08 18:07:36 -0700461 }
462
463 @Override // Binder call
Michael Wrightd86ecd22014-08-12 19:27:54 -0700464 public void start(final IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700465 if (callback == null) {
466 throw new IllegalArgumentException("callback must not be null");
467 }
468 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700469 if (isValidMediaProjection(asBinder())) {
Zimuzoef531e02018-11-01 16:04:08 +0000470 Slog.w(TAG, "UID " + Binder.getCallingUid()
471 + " attempted to start already started MediaProjection");
472 return;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700473 }
Narayan Kamath396ce722019-03-01 16:53:26 +0000474
Narayan Kamath13c60c32019-03-21 16:32:56 +0000475 if (REQUIRE_FG_SERVICE_FOR_PROJECTION
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100476 && requiresForegroundService()
Narayan Kamath396ce722019-03-01 16:53:26 +0000477 && !mActivityManagerInternal.hasRunningForegroundService(
478 uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)) {
479 throw new SecurityException("Media projections require a foreground service"
480 + " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION");
481 }
482
Jae Seoac3f8e52015-08-04 11:12:13 -0700483 mCallback = callback;
484 registerCallback(mCallback);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700485 try {
486 mToken = callback.asBinder();
Michael Wrightd86ecd22014-08-12 19:27:54 -0700487 mDeathEater = new IBinder.DeathRecipient() {
488 @Override
489 public void binderDied() {
490 mCallbackDelegate.remove(callback);
491 stop();
492 }
493 };
494 mToken.linkToDeath(mDeathEater, 0);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700495 } catch (RemoteException e) {
496 Slog.w(TAG,
497 "MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
498 return;
499 }
Alan Stokesb3d4b642020-04-24 17:08:26 +0100500 if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
501 final long token = Binder.clearCallingIdentity();
502 try {
503 // We allow an app running a current screen capture session to use
504 // SYSTEM_ALERT_WINDOW for the duration of the session, to enable
505 // them to overlay their UX on top of what is being captured.
506 // We only do this if the app requests the permission, and the appop
507 // is in its default state (the user has neither explicitly allowed nor
508 // disallowed it).
509 final PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(
510 packageName, PackageManager.GET_PERMISSIONS,
511 UserHandle.getUserId(uid));
512 if (ArrayUtils.contains(packageInfo.requestedPermissions,
513 Manifest.permission.SYSTEM_ALERT_WINDOW)) {
514 final int currentMode = mAppOps.unsafeCheckOpRawNoThrow(
515 AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName);
516 if (currentMode == AppOpsManager.MODE_DEFAULT) {
517 mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid,
518 packageName, AppOpsManager.MODE_ALLOWED);
519 mRestoreSystemAlertWindow = true;
520 }
521 }
522 } catch (PackageManager.NameNotFoundException e) {
523 Slog.w(TAG, "Package not found, aborting MediaProjection", e);
524 return;
525 } finally {
526 Binder.restoreCallingIdentity(token);
527 }
528 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700529 startProjectionLocked(this);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700530 }
531 }
532
533 @Override // Binder call
534 public void stop() {
535 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700536 if (!isValidMediaProjection(asBinder())) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700537 Slog.w(TAG, "Attempted to stop inactive MediaProjection "
538 + "(uid=" + Binder.getCallingUid() + ", "
539 + "pid=" + Binder.getCallingPid() + ")");
540 return;
541 }
Alan Stokesb3d4b642020-04-24 17:08:26 +0100542 if (mRestoreSystemAlertWindow) {
543 final long token = Binder.clearCallingIdentity();
544 try {
545 // Put the appop back how it was, unless it has been changed from what
546 // we set it to.
547 // Note that WindowManager takes care of removing any existing overlay
548 // windows when we do this.
549 final int currentMode = mAppOps.unsafeCheckOpRawNoThrow(
550 AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName);
551 if (currentMode == AppOpsManager.MODE_ALLOWED) {
552 mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName,
553 AppOpsManager.MODE_DEFAULT);
554 }
555 mRestoreSystemAlertWindow = false;
556 } finally {
557 Binder.restoreCallingIdentity(token);
558 }
559 }
Michael Wrightd86ecd22014-08-12 19:27:54 -0700560 stopProjectionLocked(this);
Jae Seoac3f8e52015-08-04 11:12:13 -0700561 mToken.unlinkToDeath(mDeathEater, 0);
562 mToken = null;
563 unregisterCallback(mCallback);
564 mCallback = null;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700565 }
566 }
567
568 @Override
Michael Wrightcde5bb42014-09-08 13:26:34 -0700569 public void registerCallback(IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700570 if (callback == null) {
571 throw new IllegalArgumentException("callback must not be null");
572 }
573 mCallbackDelegate.add(callback);
574 }
575
576 @Override
Michael Wrightcde5bb42014-09-08 13:26:34 -0700577 public void unregisterCallback(IMediaProjectionCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700578 if (callback == null) {
579 throw new IllegalArgumentException("callback must not be null");
580 }
581 mCallbackDelegate.remove(callback);
582 }
583
Michael Wrightd86ecd22014-08-12 19:27:54 -0700584 public MediaProjectionInfo getProjectionInfo() {
585 return new MediaProjectionInfo(packageName, userHandle);
586 }
587
Narayan Kamathf3bbcfe2019-04-01 19:26:46 +0100588 boolean requiresForegroundService() {
589 return mTargetSdkVersion >= Build.VERSION_CODES.Q && !mIsPrivileged;
590 }
591
Michael Wrightd86ecd22014-08-12 19:27:54 -0700592 public void dump(PrintWriter pw) {
593 pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
Michael Wrightc39d47a2014-07-08 18:07:36 -0700594 }
595 }
596
Michael Wright89c2cb62014-09-14 13:06:34 -0700597 private class MediaRouterCallback extends MediaRouter.SimpleCallback {
598 @Override
599 public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) {
600 synchronized (mLock) {
601 if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
602 mMediaRouteInfo = info;
603 if (mProjectionGrant != null) {
604 mProjectionGrant.stop();
605 }
606 }
607 }
608 }
609
610 @Override
611 public void onRouteUnselected(MediaRouter route, int type, MediaRouter.RouteInfo info) {
612 if (mMediaRouteInfo == info) {
613 mMediaRouteInfo = null;
614 }
615 }
616 }
617
Michael Wrightd86ecd22014-08-12 19:27:54 -0700618
Michael Wrightc39d47a2014-07-08 18:07:36 -0700619 private static class CallbackDelegate {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700620 private Map<IBinder, IMediaProjectionCallback> mClientCallbacks;
621 private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks;
Michael Wrightc39d47a2014-07-08 18:07:36 -0700622 private Handler mHandler;
623 private Object mLock = new Object();
624
625 public CallbackDelegate() {
626 mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
Michael Wrightd86ecd22014-08-12 19:27:54 -0700627 mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
628 mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
Michael Wrightc39d47a2014-07-08 18:07:36 -0700629 }
630
631 public void add(IMediaProjectionCallback callback) {
632 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700633 mClientCallbacks.put(callback.asBinder(), callback);
634 }
635 }
636
637 public void add(IMediaProjectionWatcherCallback callback) {
638 synchronized (mLock) {
639 mWatcherCallbacks.put(callback.asBinder(), callback);
Michael Wrightc39d47a2014-07-08 18:07:36 -0700640 }
641 }
642
643 public void remove(IMediaProjectionCallback callback) {
644 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700645 mClientCallbacks.remove(callback.asBinder());
Michael Wrightc39d47a2014-07-08 18:07:36 -0700646 }
647 }
648
Michael Wrightd86ecd22014-08-12 19:27:54 -0700649 public void remove(IMediaProjectionWatcherCallback callback) {
Michael Wrightc39d47a2014-07-08 18:07:36 -0700650 synchronized (mLock) {
Michael Wrightd86ecd22014-08-12 19:27:54 -0700651 mWatcherCallbacks.remove(callback.asBinder());
652 }
653 }
654
655 public void dispatchStart(MediaProjection projection) {
656 if (projection == null) {
657 Slog.e(TAG, "Tried to dispatch start notification for a null media projection."
658 + " Ignoring!");
659 return;
660 }
661 synchronized (mLock) {
662 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
663 MediaProjectionInfo info = projection.getProjectionInfo();
664 mHandler.post(new WatcherStartCallback(info, callback));
665 }
666 }
667 }
668
669 public void dispatchStop(MediaProjection projection) {
670 if (projection == null) {
671 Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
672 + " Ignoring!");
673 return;
674 }
675 synchronized (mLock) {
676 for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
677 mHandler.post(new ClientStopCallback(callback));
678 }
679
680 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
681 MediaProjectionInfo info = projection.getProjectionInfo();
682 mHandler.post(new WatcherStopCallback(info, callback));
Michael Wrightc39d47a2014-07-08 18:07:36 -0700683 }
684 }
685 }
686 }
687
Michael Wrightd86ecd22014-08-12 19:27:54 -0700688 private static final class WatcherStartCallback implements Runnable {
689 private IMediaProjectionWatcherCallback mCallback;
690 private MediaProjectionInfo mInfo;
691
692 public WatcherStartCallback(MediaProjectionInfo info,
693 IMediaProjectionWatcherCallback callback) {
694 mInfo = info;
695 mCallback = callback;
696 }
697
698 @Override
699 public void run() {
700 try {
701 mCallback.onStart(mInfo);
702 } catch (RemoteException e) {
703 Slog.w(TAG, "Failed to notify media projection has stopped", e);
704 }
705 }
706 }
707
708 private static final class WatcherStopCallback implements Runnable {
709 private IMediaProjectionWatcherCallback mCallback;
710 private MediaProjectionInfo mInfo;
711
712 public WatcherStopCallback(MediaProjectionInfo info,
713 IMediaProjectionWatcherCallback callback) {
714 mInfo = info;
715 mCallback = callback;
716 }
717
718 @Override
719 public void run() {
720 try {
721 mCallback.onStop(mInfo);
722 } catch (RemoteException e) {
723 Slog.w(TAG, "Failed to notify media projection has stopped", e);
724 }
725 }
726 }
727
728 private static final class ClientStopCallback implements Runnable {
729 private IMediaProjectionCallback mCallback;
730
731 public ClientStopCallback(IMediaProjectionCallback callback) {
732 mCallback = callback;
733 }
734
735 @Override
736 public void run() {
737 try {
738 mCallback.onStop();
739 } catch (RemoteException e) {
740 Slog.w(TAG, "Failed to notify media projection has stopped", e);
741 }
742 }
743 }
744
745
Michael Wrightc39d47a2014-07-08 18:07:36 -0700746 private static String typeToString(int type) {
747 switch (type) {
748 case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
749 return "TYPE_SCREEN_CAPTURE";
750 case MediaProjectionManager.TYPE_MIRRORING:
751 return "TYPE_MIRRORING";
752 case MediaProjectionManager.TYPE_PRESENTATION:
753 return "TYPE_PRESENTATION";
754 }
755 return Integer.toString(type);
756 }
757}