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