MediaProjectionManagerService.java revision 6720be4e8c65e90d4453ddad5cef192bc3820038
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
17package com.android.server.media.projection;
18
19import com.android.server.Watchdog;
20
21import android.Manifest;
22import android.app.AppOpsManager;
23import android.content.Context;
24import android.content.pm.PackageManager;
25import android.hardware.display.DisplayManager;
26import android.media.projection.IMediaProjectionManager;
27import android.media.projection.IMediaProjection;
28import android.media.projection.IMediaProjectionCallback;
29import android.media.projection.MediaProjectionManager;
30import android.os.Binder;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.IBinder.DeathRecipient;
34import android.os.Looper;
35import android.os.Message;
36import android.os.RemoteException;
37import android.util.ArrayMap;
38import android.util.Slog;
39
40import com.android.server.SystemService;
41
42import java.io.FileDescriptor;
43import java.io.PrintWriter;
44import java.util.ArrayList;
45import java.util.Collection;
46import java.util.List;
47import java.util.Map;
48
49/**
50 * Manages MediaProjection sessions.
51 *
52 * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
53 * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
54 * grants <b>must</b> validate the token before use by calling {@link
55 * IMediaProjectionService#isValidMediaProjection}.
56 */
57public final class MediaProjectionManagerService extends SystemService
58        implements Watchdog.Monitor {
59    private static final String TAG = "MediaProjectionManagerService";
60
61    private final Object mLock = new Object(); // Protects the list of media projections
62    private final Map<IBinder, MediaProjection> mProjectionGrants;
63
64    private final Context mContext;
65    private final AppOpsManager mAppOps;
66
67    public MediaProjectionManagerService(Context context) {
68        super(context);
69        mContext = context;
70        mProjectionGrants = new ArrayMap<IBinder, MediaProjection>();
71        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
72        Watchdog.getInstance().addMonitor(this);
73    }
74
75    @Override
76    public void onStart() {
77        publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
78                false /*allowIsolated*/);
79    }
80
81    @Override
82    public void monitor() {
83        synchronized (mLock) { /* check for deadlock */ }
84    }
85
86    private void dump(final PrintWriter pw) {
87        pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
88        synchronized (mLock) {
89            Collection<MediaProjection> projections = mProjectionGrants.values();
90            pw.println("Media Projections: size=" + projections.size());
91            for (MediaProjection mp : projections) {
92                mp.dump(pw, "  ");
93            }
94        }
95    }
96
97    private final class BinderService extends IMediaProjectionManager.Stub {
98
99        @Override // Binder call
100        public boolean hasProjectionPermission(int uid, String packageName) {
101            long token = Binder.clearCallingIdentity();
102            boolean hasPermission = false;
103            try {
104                hasPermission |= checkPermission(packageName,
105                        android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
106                        || mAppOps.noteOpNoThrow(
107                                AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
108                        == AppOpsManager.MODE_ALLOWED;
109            } finally {
110                Binder.restoreCallingIdentity(token);
111            }
112            return hasPermission;
113        }
114
115        @Override // Binder call
116        public IMediaProjection createProjection(int uid, String packageName, int type,
117                boolean isPermanentGrant) {
118            if (mContext.checkCallingPermission(Manifest.permission.CREATE_MEDIA_PROJECTION)
119                        != PackageManager.PERMISSION_GRANTED) {
120                throw new SecurityException("Requires CREATE_MEDIA_PROJECTION in order to grant "
121                        + "projection permission");
122            }
123            long callingToken = Binder.clearCallingIdentity();
124            MediaProjection projection;
125            try {
126                projection = new MediaProjection(type, uid, packageName);
127                if (isPermanentGrant) {
128                    mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
129                            projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
130                }
131            } finally {
132                Binder.restoreCallingIdentity(callingToken);
133            }
134            return projection;
135        }
136
137        @Override // Binder call
138        public boolean isValidMediaProjection(IMediaProjection projection) {
139            return mProjectionGrants.containsKey(projection.asBinder());
140        }
141
142        @Override // Binder call
143        public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
144            if (mContext == null
145                    || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
146                    != PackageManager.PERMISSION_GRANTED) {
147                pw.println("Permission Denial: can't dump MediaProjectionManager from from pid="
148                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
149                return;
150            }
151
152            final long token = Binder.clearCallingIdentity();
153            try {
154                dump(pw);
155            } finally {
156                Binder.restoreCallingIdentity(token);
157            }
158        }
159
160        private boolean checkPermission(String packageName, String permission) {
161            return mContext.getPackageManager().checkPermission(permission, packageName)
162                    == PackageManager.PERMISSION_GRANTED;
163        }
164    }
165
166    private final class MediaProjection extends IMediaProjection.Stub implements DeathRecipient {
167        public int uid;
168        public String packageName;
169
170        private IBinder mToken;
171        private int mType;
172        private CallbackDelegate mCallbackDelegate;
173
174        public MediaProjection(int type, int uid, String packageName) {
175            mType = type;
176            this.uid = uid;
177            this.packageName = packageName;
178            mCallbackDelegate = new CallbackDelegate();
179        }
180
181        @Override // Binder call
182        public boolean canProjectVideo() {
183            return mType == MediaProjectionManager.TYPE_MIRRORING ||
184                    mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
185        }
186
187        @Override // Binder call
188        public boolean canProjectSecureVideo() {
189            return false;
190        }
191
192        @Override // Binder call
193        public boolean canProjectAudio() {
194            return mType == MediaProjectionManager.TYPE_MIRRORING ||
195                    mType == MediaProjectionManager.TYPE_PRESENTATION;
196        }
197
198        @Override // Binder call
199        public int applyVirtualDisplayFlags(int flags) {
200            if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
201                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
202                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
203                        | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
204                return flags;
205            } else if (mType == MediaProjectionManager.TYPE_MIRRORING) {
206                flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
207                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
208                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
209                        DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
210                return flags;
211            } else if (mType == MediaProjectionManager.TYPE_PRESENTATION) {
212                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
213                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
214                        DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
215                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
216                return flags;
217            } else  {
218                throw new RuntimeException("Unknown MediaProjection type");
219            }
220        }
221
222        @Override // Binder call
223        public void start(IMediaProjectionCallback callback) {
224            if (callback == null) {
225                throw new IllegalArgumentException("callback must not be null");
226            }
227            synchronized (mLock) {
228                if (mProjectionGrants.containsKey(asBinder())) {
229                    throw new IllegalStateException(
230                            "Cannot start already started MediaProjection");
231                }
232                addCallback(callback);
233                try {
234                    mToken = callback.asBinder();
235                    mToken.linkToDeath(this, 0);
236                } catch (RemoteException e) {
237                    Slog.w(TAG,
238                            "MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
239                    return;
240                }
241                mProjectionGrants.put(asBinder(), this);
242            }
243        }
244
245        @Override // Binder call
246        public void stop() {
247            synchronized (mLock) {
248                if (!mProjectionGrants.containsKey(asBinder())) {
249                    Slog.w(TAG, "Attempted to stop inactive MediaProjection "
250                            + "(uid=" + Binder.getCallingUid() + ", "
251                            + "pid=" + Binder.getCallingPid() + ")");
252                    return;
253                }
254                mToken.unlinkToDeath(this, 0);
255                mCallbackDelegate.dispatchStop();
256                mProjectionGrants.remove(asBinder());
257            }
258        }
259
260        @Override
261        public void binderDied() {
262            stop();
263        }
264
265        @Override
266        public void addCallback(IMediaProjectionCallback callback) {
267            if (callback == null) {
268                throw new IllegalArgumentException("callback must not be null");
269            }
270            mCallbackDelegate.add(callback);
271        }
272
273        @Override
274        public void removeCallback(IMediaProjectionCallback callback) {
275            if (callback == null) {
276                throw new IllegalArgumentException("callback must not be null");
277            }
278            mCallbackDelegate.remove(callback);
279        }
280
281        public void dump(PrintWriter pw, String prefix) {
282            pw.println(prefix + "(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
283        }
284    }
285
286    private static class CallbackDelegate {
287        private static final int MSG_ON_STOP = 0;
288        private List<IMediaProjectionCallback> mCallbacks;
289        private Handler mHandler;
290        private Object mLock = new Object();
291
292        public CallbackDelegate() {
293            mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
294            mCallbacks = new ArrayList<IMediaProjectionCallback>();
295        }
296
297        public void add(IMediaProjectionCallback callback) {
298            synchronized (mLock) {
299                mCallbacks.add(callback);
300            }
301        }
302
303        public void remove(IMediaProjectionCallback callback) {
304            synchronized (mLock) {
305                mCallbacks.remove(callback);
306            }
307        }
308
309        public void dispatchStop() {
310            synchronized (mLock) {
311                for (final IMediaProjectionCallback callback : mCallbacks) {
312                    mHandler.post(new Runnable() {
313                        @Override
314                        public void run() {
315                            try {
316                                callback.onStop();
317                            } catch (RemoteException e) {
318                                Slog.w(TAG, "Failed to notify media projection has stopped", e);
319                            }
320                        }
321                    });
322                }
323            }
324        }
325    }
326
327    private static String typeToString(int type) {
328        switch (type) {
329            case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
330                return "TYPE_SCREEN_CAPTURE";
331            case MediaProjectionManager.TYPE_MIRRORING:
332                return "TYPE_MIRRORING";
333            case MediaProjectionManager.TYPE_PRESENTATION:
334                return "TYPE_PRESENTATION";
335        }
336        return Integer.toString(type);
337    }
338}
339