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