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 android.media.projection; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemService; 22 import android.app.Activity; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.util.ArrayMap; 31 import android.util.Log; 32 33 import java.util.Map; 34 35 /** 36 * Manages the retrieval of certain types of {@link MediaProjection} tokens. 37 */ 38 @SystemService(Context.MEDIA_PROJECTION_SERVICE) 39 public final class MediaProjectionManager { 40 private static final String TAG = "MediaProjectionManager"; 41 /** @hide */ 42 public static final String EXTRA_APP_TOKEN = "android.media.projection.extra.EXTRA_APP_TOKEN"; 43 /** @hide */ 44 public static final String EXTRA_MEDIA_PROJECTION = 45 "android.media.projection.extra.EXTRA_MEDIA_PROJECTION"; 46 47 /** @hide */ 48 public static final int TYPE_SCREEN_CAPTURE = 0; 49 /** @hide */ 50 public static final int TYPE_MIRRORING = 1; 51 /** @hide */ 52 public static final int TYPE_PRESENTATION = 2; 53 54 private Context mContext; 55 private Map<Callback, CallbackDelegate> mCallbacks; 56 private IMediaProjectionManager mService; 57 58 /** @hide */ MediaProjectionManager(Context context)59 public MediaProjectionManager(Context context) { 60 mContext = context; 61 IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); 62 mService = IMediaProjectionManager.Stub.asInterface(b); 63 mCallbacks = new ArrayMap<>(); 64 } 65 66 /** 67 * Returns an Intent that <b>must</b> be passed to startActivityForResult() 68 * in order to start screen capture. The activity will prompt 69 * the user whether to allow screen capture. The result of this 70 * activity should be passed to getMediaProjection. 71 */ createScreenCaptureIntent()72 public Intent createScreenCaptureIntent() { 73 Intent i = new Intent(); 74 final ComponentName mediaProjectionPermissionDialogComponent = 75 ComponentName.unflattenFromString(mContext.getResources().getString( 76 com.android.internal.R.string 77 .config_mediaProjectionPermissionDialogComponent)); 78 i.setComponent(mediaProjectionPermissionDialogComponent); 79 return i; 80 } 81 82 /** 83 * Retrieve the MediaProjection obtained from a succesful screen 84 * capture request. Will be null if the result from the 85 * startActivityForResult() is anything other than RESULT_OK. 86 * 87 * Starting from Android {@link android.os.Build.VERSION_CODES#R}, if your application requests 88 * the {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission, and the 89 * user has not explicitly denied it, the permission will be automatically granted until the 90 * projection is stopped. This allows for user controls to be displayed on top of the screen 91 * being captured. 92 * 93 * <p> 94 * Apps targeting SDK version {@link android.os.Build.VERSION_CODES#Q} or later should specify 95 * the foreground service type using the attribute {@link android.R.attr#foregroundServiceType} 96 * in the service element of the app's manifest file. 97 * The {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION} attribute 98 * should be specified. 99 * </p> 100 * 101 * @see <a href="https://developer.android.com/preview/privacy/foreground-service-types"> 102 * Foregroud Service Types</a> 103 * 104 * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, 105 * int, android.content.Intent)} 106 * @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int, 107 * int, android.content.Intent)} 108 * @throws IllegalStateException on pre-Q devices if a previously gotten MediaProjection 109 * from the same {@code resultData} has not yet been stopped 110 */ getMediaProjection(int resultCode, @NonNull Intent resultData)111 public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) { 112 if (resultCode != Activity.RESULT_OK || resultData == null) { 113 return null; 114 } 115 IBinder projection = resultData.getIBinderExtra(EXTRA_MEDIA_PROJECTION); 116 if (projection == null) { 117 return null; 118 } 119 return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection)); 120 } 121 122 /** 123 * Get the {@link MediaProjectionInfo} for the active {@link MediaProjection}. 124 * @hide 125 */ getActiveProjectionInfo()126 public MediaProjectionInfo getActiveProjectionInfo() { 127 try { 128 return mService.getActiveProjectionInfo(); 129 } catch (RemoteException e) { 130 Log.e(TAG, "Unable to get the active projection info", e); 131 } 132 return null; 133 } 134 135 /** 136 * Stop the current projection if there is one. 137 * @hide 138 */ stopActiveProjection()139 public void stopActiveProjection() { 140 try { 141 mService.stopActiveProjection(); 142 } catch (RemoteException e) { 143 Log.e(TAG, "Unable to stop the currently active media projection", e); 144 } 145 } 146 147 /** 148 * Add a callback to monitor all of the {@link MediaProjection}s activity. 149 * Not for use by regular applications, must have the MANAGE_MEDIA_PROJECTION permission. 150 * @hide 151 */ addCallback(@onNull Callback callback, @Nullable Handler handler)152 public void addCallback(@NonNull Callback callback, @Nullable Handler handler) { 153 if (callback == null) { 154 throw new IllegalArgumentException("callback must not be null"); 155 } 156 CallbackDelegate delegate = new CallbackDelegate(callback, handler); 157 mCallbacks.put(callback, delegate); 158 try { 159 mService.addCallback(delegate); 160 } catch (RemoteException e) { 161 Log.e(TAG, "Unable to add callbacks to MediaProjection service", e); 162 } 163 } 164 165 /** 166 * Remove a MediaProjection monitoring callback. 167 * @hide 168 */ removeCallback(@onNull Callback callback)169 public void removeCallback(@NonNull Callback callback) { 170 if (callback == null) { 171 throw new IllegalArgumentException("callback must not be null"); 172 } 173 CallbackDelegate delegate = mCallbacks.remove(callback); 174 try { 175 if (delegate != null) { 176 mService.removeCallback(delegate); 177 } 178 } catch (RemoteException e) { 179 Log.e(TAG, "Unable to add callbacks to MediaProjection service", e); 180 } 181 } 182 183 /** @hide */ 184 public static abstract class Callback { onStart(MediaProjectionInfo info)185 public abstract void onStart(MediaProjectionInfo info); onStop(MediaProjectionInfo info)186 public abstract void onStop(MediaProjectionInfo info); 187 } 188 189 /** @hide */ 190 private final static class CallbackDelegate extends IMediaProjectionWatcherCallback.Stub { 191 private Callback mCallback; 192 private Handler mHandler; 193 CallbackDelegate(Callback callback, Handler handler)194 public CallbackDelegate(Callback callback, Handler handler) { 195 mCallback = callback; 196 if (handler == null) { 197 handler = new Handler(); 198 } 199 mHandler = handler; 200 } 201 202 @Override onStart(final MediaProjectionInfo info)203 public void onStart(final MediaProjectionInfo info) { 204 mHandler.post(new Runnable() { 205 @Override 206 public void run() { 207 mCallback.onStart(info); 208 } 209 }); 210 } 211 212 @Override onStop(final MediaProjectionInfo info)213 public void onStop(final MediaProjectionInfo info) { 214 mHandler.post(new Runnable() { 215 @Override 216 public void run() { 217 mCallback.onStop(info); 218 } 219 }); 220 } 221 } 222 } 223