OSDN Git Service

Merge "Do not enforce CONTROL_VPN for calls from lockdown VPN." into lmp-mr1-dev
[android-x86/frameworks-base.git] / services / core / java / com / android / server / media / projection / MediaProjectionManagerService.java
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.server.media.projection;
18
19 import com.android.server.Watchdog;
20
21 import android.Manifest;
22 import android.app.AppOpsManager;
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.hardware.display.DisplayManager;
26 import android.media.MediaRouter;
27 import android.media.projection.IMediaProjectionManager;
28 import android.media.projection.IMediaProjection;
29 import android.media.projection.IMediaProjectionCallback;
30 import android.media.projection.IMediaProjectionWatcherCallback;
31 import android.media.projection.MediaProjectionInfo;
32 import android.media.projection.MediaProjectionManager;
33 import android.os.Binder;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.IBinder.DeathRecipient;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.RemoteException;
40 import android.os.UserHandle;
41 import android.util.ArrayMap;
42 import android.util.Slog;
43
44 import com.android.server.SystemService;
45
46 import java.io.FileDescriptor;
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.List;
51 import java.util.Map;
52
53 /**
54  * Manages MediaProjection sessions.
55  *
56  * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
57  * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
58  * grants <b>must</b> validate the token before use by calling {@link
59  * IMediaProjectionService#isValidMediaProjection}.
60  */
61 public final class MediaProjectionManagerService extends SystemService
62         implements Watchdog.Monitor {
63     private static final String TAG = "MediaProjectionManagerService";
64
65     private final Object mLock = new Object(); // Protects the list of media projections
66     private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;
67     private final CallbackDelegate mCallbackDelegate;
68
69     private final Context mContext;
70     private final AppOpsManager mAppOps;
71
72     private final MediaRouter mMediaRouter;
73     private final MediaRouterCallback mMediaRouterCallback;
74     private MediaRouter.RouteInfo mMediaRouteInfo;
75
76     private IBinder mProjectionToken;
77     private MediaProjection mProjectionGrant;
78
79     public MediaProjectionManagerService(Context context) {
80         super(context);
81         mContext = context;
82         mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
83         mCallbackDelegate = new CallbackDelegate();
84         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
85         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
86         mMediaRouterCallback = new MediaRouterCallback();
87         Watchdog.getInstance().addMonitor(this);
88     }
89
90     @Override
91     public void onStart() {
92         publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
93                 false /*allowIsolated*/);
94         mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
95                 MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
96     }
97
98     @Override
99     public void onSwitchUser(int userId) {
100         mMediaRouter.rebindAsUser(userId);
101     }
102
103     @Override
104     public void monitor() {
105         synchronized (mLock) { /* check for deadlock */ }
106     }
107
108     private void startProjectionLocked(final MediaProjection projection) {
109         if (mProjectionGrant != null) {
110             mProjectionGrant.stop();
111         }
112         if (mMediaRouteInfo != null) {
113             mMediaRouter.getDefaultRoute().select();
114         }
115         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() {
130                 synchronized (mLock) {
131                     removeCallback(callback);
132                 }
133             }
134         };
135         synchronized (mLock) {
136             mCallbackDelegate.add(callback);
137             linkDeathRecipientLocked(callback, deathRecipient);
138         }
139     }
140
141     private void removeCallback(IMediaProjectionWatcherCallback callback) {
142         synchronized (mLock) {
143             unlinkDeathRecipientLocked(callback);
144             mCallbackDelegate.remove(callback);
145         }
146     }
147
148     private void linkDeathRecipientLocked(IMediaProjectionWatcherCallback callback,
149             IBinder.DeathRecipient deathRecipient) {
150         try {
151             final IBinder token = callback.asBinder();
152             token.linkToDeath(deathRecipient, 0);
153             mDeathEaters.put(token, deathRecipient);
154         } catch (RemoteException e) {
155             Slog.e(TAG, "Unable to link to death for media projection monitoring callback", e);
156         }
157     }
158
159     private void unlinkDeathRecipientLocked(IMediaProjectionWatcherCallback callback) {
160         final IBinder token = callback.asBinder();
161         IBinder.DeathRecipient deathRecipient = mDeathEaters.remove(token);
162         if (deathRecipient != null) {
163             token.unlinkToDeath(deathRecipient, 0);
164         }
165     }
166
167     private void dispatchStart(MediaProjection projection) {
168         mCallbackDelegate.dispatchStart(projection);
169     }
170
171     private void dispatchStop(MediaProjection projection) {
172         mCallbackDelegate.dispatchStop(projection);
173     }
174
175     private boolean isValidMediaProjection(IBinder token) {
176         synchronized (mLock) {
177             if (mProjectionToken != null) {
178                 return mProjectionToken.equals(token);
179             }
180             return false;
181         }
182     }
183
184     private MediaProjectionInfo getActiveProjectionInfo() {
185         synchronized (mLock) {
186             if (mProjectionGrant == null) {
187                 return null;
188             }
189             return mProjectionGrant.getProjectionInfo();
190         }
191     }
192
193     private void dump(final PrintWriter pw) {
194         pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
195         synchronized (mLock) {
196             pw.println("Media Projection: ");
197             if (mProjectionGrant != null ) {
198                 mProjectionGrant.dump(pw);
199             } else {
200                 pw.println("null");
201             }
202         }
203     }
204
205     private final class BinderService extends IMediaProjectionManager.Stub {
206
207         @Override // Binder call
208         public boolean hasProjectionPermission(int uid, String packageName) {
209             long token = Binder.clearCallingIdentity();
210             boolean hasPermission = false;
211             try {
212                 hasPermission |= checkPermission(packageName,
213                         android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
214                         || mAppOps.noteOpNoThrow(
215                                 AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
216                         == AppOpsManager.MODE_ALLOWED;
217             } finally {
218                 Binder.restoreCallingIdentity(token);
219             }
220             return hasPermission;
221         }
222
223         @Override // Binder call
224         public IMediaProjection createProjection(int uid, String packageName, int type,
225                 boolean isPermanentGrant) {
226             if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
227                         != PackageManager.PERMISSION_GRANTED) {
228                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
229                         + "projection permission");
230             }
231             if (packageName == null || packageName.isEmpty()) {
232                 throw new IllegalArgumentException("package name must not be empty");
233             }
234             long callingToken = Binder.clearCallingIdentity();
235             MediaProjection projection;
236             try {
237                 projection = new MediaProjection(type, uid, packageName);
238                 if (isPermanentGrant) {
239                     mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
240                             projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
241                 }
242             } finally {
243                 Binder.restoreCallingIdentity(callingToken);
244             }
245             return projection;
246         }
247
248         @Override // Binder call
249         public boolean isValidMediaProjection(IMediaProjection projection) {
250             return MediaProjectionManagerService.this.isValidMediaProjection(
251                     projection.asBinder());
252         }
253
254         @Override // Binder call
255         public MediaProjectionInfo getActiveProjectionInfo() {
256             if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
257                         != PackageManager.PERMISSION_GRANTED) {
258                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
259                         + "projection callbacks");
260             }
261             final long token = Binder.clearCallingIdentity();
262             try {
263                 return MediaProjectionManagerService.this.getActiveProjectionInfo();
264             } finally {
265                 Binder.restoreCallingIdentity(token);
266             }
267         }
268
269         @Override // Binder call
270         public void stopActiveProjection() {
271             if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
272                         != PackageManager.PERMISSION_GRANTED) {
273                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
274                         + "projection callbacks");
275             }
276             final long token = Binder.clearCallingIdentity();
277             try {
278                 if (mProjectionGrant != null) {
279                     mProjectionGrant.stop();
280                 }
281             } finally {
282                 Binder.restoreCallingIdentity(token);
283             }
284
285         }
286
287         @Override //Binder call
288         public void addCallback(final IMediaProjectionWatcherCallback callback) {
289             if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
290                         != PackageManager.PERMISSION_GRANTED) {
291                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
292                         + "projection callbacks");
293             }
294             final long token = Binder.clearCallingIdentity();
295             try {
296                 MediaProjectionManagerService.this.addCallback(callback);
297             } finally {
298                 Binder.restoreCallingIdentity(token);
299             }
300         }
301
302         @Override
303         public void removeCallback(IMediaProjectionWatcherCallback callback) {
304             if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
305                         != PackageManager.PERMISSION_GRANTED) {
306                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove "
307                         + "projection callbacks");
308             }
309             final long token = Binder.clearCallingIdentity();
310             try {
311                 MediaProjectionManagerService.this.removeCallback(callback);
312             } finally {
313                 Binder.restoreCallingIdentity(token);
314             }
315         }
316
317         @Override // Binder call
318         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
319             if (mContext == null
320                     || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
321                     != PackageManager.PERMISSION_GRANTED) {
322                 pw.println("Permission Denial: can't dump MediaProjectionManager from from pid="
323                         + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
324                 return;
325             }
326
327             final long token = Binder.clearCallingIdentity();
328             try {
329                 MediaProjectionManagerService.this.dump(pw);
330             } finally {
331                 Binder.restoreCallingIdentity(token);
332             }
333         }
334
335
336         private boolean checkPermission(String packageName, String permission) {
337             return mContext.getPackageManager().checkPermission(permission, packageName)
338                     == PackageManager.PERMISSION_GRANTED;
339         }
340     }
341
342     private final class MediaProjection extends IMediaProjection.Stub {
343         public final int uid;
344         public final String packageName;
345         public final UserHandle userHandle;
346
347         private IBinder mToken;
348         private IBinder.DeathRecipient mDeathEater;
349         private int mType;
350
351         public MediaProjection(int type, int uid, String packageName) {
352             mType = type;
353             this.uid = uid;
354             this.packageName = packageName;
355             userHandle = new UserHandle(UserHandle.getUserId(uid));
356         }
357
358         @Override // Binder call
359         public boolean canProjectVideo() {
360             return mType == MediaProjectionManager.TYPE_MIRRORING ||
361                     mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
362         }
363
364         @Override // Binder call
365         public boolean canProjectSecureVideo() {
366             return false;
367         }
368
369         @Override // Binder call
370         public boolean canProjectAudio() {
371             return mType == MediaProjectionManager.TYPE_MIRRORING ||
372                     mType == MediaProjectionManager.TYPE_PRESENTATION;
373         }
374
375         @Override // Binder call
376         public int applyVirtualDisplayFlags(int flags) {
377             if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
378                 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
379                 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
380                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
381                 return flags;
382             } else if (mType == MediaProjectionManager.TYPE_MIRRORING) {
383                 flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
384                         DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
385                 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
386                         DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
387                 return flags;
388             } else if (mType == MediaProjectionManager.TYPE_PRESENTATION) {
389                 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
390                 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
391                         DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
392                         DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
393                 return flags;
394             } else  {
395                 throw new RuntimeException("Unknown MediaProjection type");
396             }
397         }
398
399         @Override // Binder call
400         public void start(final IMediaProjectionCallback callback) {
401             if (callback == null) {
402                 throw new IllegalArgumentException("callback must not be null");
403             }
404             synchronized (mLock) {
405                 if (isValidMediaProjection(asBinder())) {
406                     throw new IllegalStateException(
407                             "Cannot start already started MediaProjection");
408                 }
409                 registerCallback(callback);
410                 try {
411                     mToken = callback.asBinder();
412                     mDeathEater = new IBinder.DeathRecipient() {
413                         @Override
414                         public void binderDied() {
415                             mCallbackDelegate.remove(callback);
416                             stop();
417                         }
418                     };
419                     mToken.linkToDeath(mDeathEater, 0);
420                 } catch (RemoteException e) {
421                     Slog.w(TAG,
422                             "MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
423                     return;
424                 }
425                 startProjectionLocked(this);
426             }
427         }
428
429         @Override // Binder call
430         public void stop() {
431             synchronized (mLock) {
432                 if (!isValidMediaProjection(asBinder())) {
433                     Slog.w(TAG, "Attempted to stop inactive MediaProjection "
434                             + "(uid=" + Binder.getCallingUid() + ", "
435                             + "pid=" + Binder.getCallingPid() + ")");
436                     return;
437                 }
438                 mToken.unlinkToDeath(mDeathEater, 0);
439                 stopProjectionLocked(this);
440             }
441         }
442
443         @Override
444         public void registerCallback(IMediaProjectionCallback callback) {
445             if (callback == null) {
446                 throw new IllegalArgumentException("callback must not be null");
447             }
448             mCallbackDelegate.add(callback);
449         }
450
451         @Override
452         public void unregisterCallback(IMediaProjectionCallback callback) {
453             if (callback == null) {
454                 throw new IllegalArgumentException("callback must not be null");
455             }
456             mCallbackDelegate.remove(callback);
457         }
458
459         public MediaProjectionInfo getProjectionInfo() {
460             return new MediaProjectionInfo(packageName, userHandle);
461         }
462
463         public void dump(PrintWriter pw) {
464             pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
465         }
466     }
467
468     private class MediaRouterCallback extends MediaRouter.SimpleCallback {
469         @Override
470         public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) {
471             synchronized (mLock) {
472                 if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
473                     mMediaRouteInfo = info;
474                     if (mProjectionGrant != null) {
475                         mProjectionGrant.stop();
476                     }
477                 }
478             }
479         }
480
481         @Override
482         public void onRouteUnselected(MediaRouter route, int type, MediaRouter.RouteInfo info) {
483             if (mMediaRouteInfo == info) {
484                 mMediaRouteInfo = null;
485             }
486         }
487     }
488
489
490     private static class CallbackDelegate {
491         private Map<IBinder, IMediaProjectionCallback> mClientCallbacks;
492         private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks;
493         private Handler mHandler;
494         private Object mLock = new Object();
495
496         public CallbackDelegate() {
497             mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
498             mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
499             mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
500         }
501
502         public void add(IMediaProjectionCallback callback) {
503             synchronized (mLock) {
504                 mClientCallbacks.put(callback.asBinder(), callback);
505             }
506         }
507
508         public void add(IMediaProjectionWatcherCallback callback) {
509             synchronized (mLock) {
510                 mWatcherCallbacks.put(callback.asBinder(), callback);
511             }
512         }
513
514         public void remove(IMediaProjectionCallback callback) {
515             synchronized (mLock) {
516                 mClientCallbacks.remove(callback.asBinder());
517             }
518         }
519
520         public void remove(IMediaProjectionWatcherCallback callback) {
521             synchronized (mLock) {
522                 mWatcherCallbacks.remove(callback.asBinder());
523             }
524         }
525
526         public void dispatchStart(MediaProjection projection) {
527             if (projection == null) {
528                 Slog.e(TAG, "Tried to dispatch start notification for a null media projection."
529                         + " Ignoring!");
530                 return;
531             }
532             synchronized (mLock) {
533                 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
534                     MediaProjectionInfo info = projection.getProjectionInfo();
535                     mHandler.post(new WatcherStartCallback(info, callback));
536                 }
537             }
538         }
539
540         public void dispatchStop(MediaProjection projection) {
541             if (projection == null) {
542                 Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
543                         + " Ignoring!");
544                 return;
545             }
546             synchronized (mLock) {
547                 for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
548                     mHandler.post(new ClientStopCallback(callback));
549                 }
550
551                 for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
552                     MediaProjectionInfo info = projection.getProjectionInfo();
553                     mHandler.post(new WatcherStopCallback(info, callback));
554                 }
555             }
556         }
557     }
558
559     private static final class WatcherStartCallback implements Runnable {
560         private IMediaProjectionWatcherCallback mCallback;
561         private MediaProjectionInfo mInfo;
562
563         public WatcherStartCallback(MediaProjectionInfo info,
564                 IMediaProjectionWatcherCallback callback) {
565             mInfo = info;
566             mCallback = callback;
567         }
568
569         @Override
570         public void run() {
571             try {
572                 mCallback.onStart(mInfo);
573             } catch (RemoteException e) {
574                 Slog.w(TAG, "Failed to notify media projection has stopped", e);
575             }
576         }
577     }
578
579     private static final class WatcherStopCallback implements Runnable {
580         private IMediaProjectionWatcherCallback mCallback;
581         private MediaProjectionInfo mInfo;
582
583         public WatcherStopCallback(MediaProjectionInfo info,
584                 IMediaProjectionWatcherCallback callback) {
585             mInfo = info;
586             mCallback = callback;
587         }
588
589         @Override
590         public void run() {
591             try {
592                 mCallback.onStop(mInfo);
593             } catch (RemoteException e) {
594                 Slog.w(TAG, "Failed to notify media projection has stopped", e);
595             }
596         }
597     }
598
599     private static final class ClientStopCallback implements Runnable {
600         private IMediaProjectionCallback mCallback;
601
602         public ClientStopCallback(IMediaProjectionCallback callback) {
603             mCallback = callback;
604         }
605
606         @Override
607         public void run() {
608             try {
609                 mCallback.onStop();
610             } catch (RemoteException e) {
611                 Slog.w(TAG, "Failed to notify media projection has stopped", e);
612             }
613         }
614     }
615
616
617     private static String typeToString(int type) {
618         switch (type) {
619             case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
620                 return "TYPE_SCREEN_CAPTURE";
621             case MediaProjectionManager.TYPE_MIRRORING:
622                 return "TYPE_MIRRORING";
623             case MediaProjectionManager.TYPE_PRESENTATION:
624                 return "TYPE_PRESENTATION";
625         }
626         return Integer.toString(type);
627     }
628 }