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