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