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