MediaProjectionManagerService.java revision c39d47a8e7c74bd539104b0efab898ef6fc43ddf
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.checkOpNoThrow( 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 getVirtualDisplayFlags() { 200 switch (mType) { 201 case MediaProjectionManager.TYPE_SCREEN_CAPTURE: 202 return DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE; 203 case MediaProjectionManager.TYPE_MIRRORING: 204 return DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | 205 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; 206 case MediaProjectionManager.TYPE_PRESENTATION: 207 return DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION | 208 DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; 209 } 210 throw new RuntimeException("Unknown MediaProjection type"); 211 } 212 213 @Override // Binder call 214 public void start(IMediaProjectionCallback callback) { 215 if (callback == null) { 216 throw new IllegalArgumentException("callback must not be null"); 217 } 218 synchronized (mLock) { 219 if (mProjectionGrants.containsKey(asBinder())) { 220 throw new IllegalStateException( 221 "Cannot start already started MediaProjection"); 222 } 223 addCallback(callback); 224 try { 225 mToken = callback.asBinder(); 226 mToken.linkToDeath(this, 0); 227 } catch (RemoteException e) { 228 Slog.w(TAG, 229 "MediaProjectionCallbacks must be valid, aborting MediaProjection", e); 230 return; 231 } 232 mProjectionGrants.put(asBinder(), this); 233 } 234 } 235 236 @Override // Binder call 237 public void stop() { 238 synchronized (mLock) { 239 if (!mProjectionGrants.containsKey(asBinder())) { 240 Slog.w(TAG, "Attempted to stop inactive MediaProjection " 241 + "(uid=" + Binder.getCallingUid() + ", " 242 + "pid=" + Binder.getCallingPid() + ")"); 243 return; 244 } 245 mToken.unlinkToDeath(this, 0); 246 mCallbackDelegate.dispatchStop(); 247 mProjectionGrants.remove(asBinder()); 248 } 249 } 250 251 @Override 252 public void binderDied() { 253 stop(); 254 } 255 256 @Override 257 public void addCallback(IMediaProjectionCallback callback) { 258 if (callback == null) { 259 throw new IllegalArgumentException("callback must not be null"); 260 } 261 mCallbackDelegate.add(callback); 262 } 263 264 @Override 265 public void removeCallback(IMediaProjectionCallback callback) { 266 if (callback == null) { 267 throw new IllegalArgumentException("callback must not be null"); 268 } 269 mCallbackDelegate.remove(callback); 270 } 271 272 public void dump(PrintWriter pw, String prefix) { 273 pw.println(prefix + "(" + packageName + ", uid=" + uid + "): " + typeToString(mType)); 274 } 275 } 276 277 private static class CallbackDelegate { 278 private static final int MSG_ON_STOP = 0; 279 private List<IMediaProjectionCallback> mCallbacks; 280 private Handler mHandler; 281 private Object mLock = new Object(); 282 283 public CallbackDelegate() { 284 mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/); 285 mCallbacks = new ArrayList<IMediaProjectionCallback>(); 286 } 287 288 public void add(IMediaProjectionCallback callback) { 289 synchronized (mLock) { 290 mCallbacks.add(callback); 291 } 292 } 293 294 public void remove(IMediaProjectionCallback callback) { 295 synchronized (mLock) { 296 mCallbacks.remove(callback); 297 } 298 } 299 300 public void dispatchStop() { 301 synchronized (mLock) { 302 for (final IMediaProjectionCallback callback : mCallbacks) { 303 mHandler.post(new Runnable() { 304 @Override 305 public void run() { 306 try { 307 callback.onStop(); 308 } catch (RemoteException e) { 309 Slog.w(TAG, "Failed to notify media projection has stopped", e); 310 } 311 } 312 }); 313 } 314 } 315 } 316 } 317 318 private static String typeToString(int type) { 319 switch (type) { 320 case MediaProjectionManager.TYPE_SCREEN_CAPTURE: 321 return "TYPE_SCREEN_CAPTURE"; 322 case MediaProjectionManager.TYPE_MIRRORING: 323 return "TYPE_MIRRORING"; 324 case MediaProjectionManager.TYPE_PRESENTATION: 325 return "TYPE_PRESENTATION"; 326 } 327 return Integer.toString(type); 328 } 329} 330