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.content.Context;
22import android.hardware.display.DisplayManager;
23import android.hardware.display.VirtualDisplay;
24import android.media.AudioRecord;
25import android.media.projection.IMediaProjection;
26import android.media.projection.IMediaProjectionCallback;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.RemoteException;
30import android.util.ArrayMap;
31import android.util.Log;
32import android.view.Surface;
33
34import java.util.Map;
35
36/**
37 * A token granting applications the ability to capture screen contents and/or
38 * record system audio. The exact capabilities granted depend on the type of
39 * MediaProjection.
40 *
41 * <p>
42 * A screen capture session can be started through {@link
43 * MediaProjectionManager#createScreenCaptureIntent}. This grants the ability to
44 * capture screen contents, but not system audio.
45 * </p>
46 */
47public final class MediaProjection {
48    private static final String TAG = "MediaProjection";
49
50    private final IMediaProjection mImpl;
51    private final Context mContext;
52    private final Map<Callback, CallbackRecord> mCallbacks;
53
54    /** @hide */
55    public MediaProjection(Context context, IMediaProjection impl) {
56        mCallbacks = new ArrayMap<Callback, CallbackRecord>();
57        mContext = context;
58        mImpl = impl;
59        try {
60            mImpl.start(new MediaProjectionCallback());
61        } catch (RemoteException e) {
62            throw new RuntimeException("Failed to start media projection", e);
63        }
64    }
65
66    /** Register a listener to receive notifications about when the {@link
67     * MediaProjection} changes state.
68     *
69     * @param callback The callback to call.
70     * @param handler The handler on which the callback should be invoked, or
71     * null if the callback should be invoked on the calling thread's looper.
72     *
73     * @see #unregisterCallback
74     */
75    public void registerCallback(Callback callback, Handler handler) {
76        if (callback == null) {
77            throw new IllegalArgumentException("callback should not be null");
78        }
79        if (handler == null) {
80            handler = new Handler();
81        }
82        mCallbacks.put(callback, new CallbackRecord(callback, handler));
83    }
84
85    /** Unregister a MediaProjection listener.
86     *
87     * @param callback The callback to unregister.
88     *
89     * @see #registerCallback
90     */
91    public void unregisterCallback(Callback callback) {
92        if (callback == null) {
93            throw new IllegalArgumentException("callback should not be null");
94        }
95        mCallbacks.remove(callback);
96    }
97
98    /**
99     * @hide
100     */
101    public VirtualDisplay createVirtualDisplay(@NonNull String name,
102            int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
103            @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
104        DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
105        int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0;
106        return dm.createVirtualDisplay(this, name, width, height, dpi, surface,
107                    flags | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR |
108                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, callback, handler);
109    }
110
111    /**
112     * Creates a {@link android.hardware.display.VirtualDisplay} to capture the
113     * contents of the screen.
114     *
115     * @param name The name of the virtual display, must be non-empty.
116     * @param width The width of the virtual display in pixels. Must be
117     * greater than 0.
118     * @param height The height of the virtual display in pixels. Must be
119     * greater than 0.
120     * @param dpi The density of the virtual display in dpi. Must be greater
121     * than 0.
122     * @param surface The surface to which the content of the virtual display
123     * should be rendered, or null if there is none initially.
124     * @param flags A combination of virtual display flags. See {@link DisplayManager} for the full
125     * list of flags.
126     * @param callback Callback to call when the virtual display's state
127     * changes, or null if none.
128     * @param handler The {@link android.os.Handler} on which the callback should be
129     * invoked, or null if the callback should be invoked on the calling
130     * thread's main {@link android.os.Looper}.
131     *
132     * @see android.hardware.display.VirtualDisplay
133     */
134    public VirtualDisplay createVirtualDisplay(@NonNull String name,
135            int width, int height, int dpi, int flags, @Nullable Surface surface,
136            @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
137        DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
138        return dm.createVirtualDisplay(
139                    this, name, width, height, dpi, surface, flags, callback, handler);
140    }
141
142    /**
143     * Creates an AudioRecord to capture audio played back by the system.
144     * @hide
145     */
146    public AudioRecord createAudioRecord(
147            int sampleRateInHz, int channelConfig,
148            int audioFormat, int bufferSizeInBytes) {
149        return null;
150    }
151
152    /**
153     * Stops projection.
154     */
155    public void stop() {
156        try {
157            mImpl.stop();
158        } catch (RemoteException e) {
159            Log.e(TAG, "Unable to stop projection", e);
160        }
161    }
162
163    /**
164     * Get the underlying IMediaProjection.
165     * @hide
166     */
167    public IMediaProjection getProjection() {
168        return mImpl;
169    }
170
171    /**
172     * Callbacks for the projection session.
173     */
174    public static abstract class Callback {
175        /**
176         * Called when the MediaProjection session is no longer valid.
177         * <p>
178         * Once a MediaProjection has been stopped, it's up to the application to release any
179         * resources it may be holding (e.g. {@link android.hardware.display.VirtualDisplay}s).
180         * </p>
181         */
182        public void onStop() { }
183    }
184
185    private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
186        @Override
187        public void onStop() {
188            for (CallbackRecord cbr : mCallbacks.values()) {
189                cbr.onStop();
190            }
191        }
192    }
193
194    private final static class CallbackRecord {
195        private final Callback mCallback;
196        private final Handler mHandler;
197
198        public CallbackRecord(Callback callback, Handler handler) {
199            mCallback = callback;
200            mHandler = handler;
201        }
202
203        public void onStop() {
204            mHandler.post(new Runnable() {
205                @Override
206                public void run() {
207                    mCallback.onStop();
208                }
209            });
210        }
211    }
212}
213