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 com.android.server.telecom.testapps;
18
19import com.android.ex.camera2.blocking.BlockingCameraManager;
20import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
21import com.android.ex.camera2.blocking.BlockingSessionCallback;
22import com.android.server.telecom.testapps.R;
23import com.android.server.telecom.testapps.TestConnectionService.TestConnection;
24
25import android.content.Context;
26import android.graphics.SurfaceTexture;
27import android.hardware.camera2.CameraAccessException;
28import android.hardware.camera2.CameraCaptureSession;
29import android.hardware.camera2.CameraCharacteristics;
30import android.hardware.camera2.CameraDevice;
31import android.hardware.camera2.CameraManager;
32import android.hardware.camera2.CaptureFailure;
33import android.hardware.camera2.CaptureRequest;
34import android.hardware.camera2.TotalCaptureResult;
35import android.hardware.camera2.params.StreamConfigurationMap;
36import android.media.MediaPlayer;
37import android.net.Uri;
38import android.os.Handler;
39import android.telecom.Connection;
40import android.telecom.VideoProfile;
41import android.telecom.VideoProfile.CameraCapabilities;
42import android.text.TextUtils;
43import android.util.Log;
44import android.util.Size;
45import android.view.Surface;
46
47import java.lang.IllegalArgumentException;
48import java.lang.String;
49import java.util.ArrayList;
50import java.util.List;
51import java.util.Random;
52
53/**
54 * Implements the VideoCallProvider.
55 */
56public class TestVideoProvider extends Connection.VideoProvider {
57    private TestConnection mConnection;
58    private CameraCapabilities mCameraCapabilities;
59    private Random random;
60    private Surface mDisplaySurface;
61    private Surface mPreviewSurface;
62    private Context mContext;
63    /** Used to play incoming video during a call. */
64    private MediaPlayer mIncomingMediaPlayer;
65
66    private CameraManager mCameraManager;
67    private CameraDevice mCameraDevice;
68    private CameraCaptureSession mCameraSession;
69    private CameraThread mLooperThread;
70
71    private final Handler mHandler = new Handler();
72
73    private String mCameraId;
74
75    private static final long SESSION_TIMEOUT_MS = 2000;
76
77    public TestVideoProvider(Context context, TestConnection connection) {
78        mConnection = connection;
79        mContext = context;
80        random = new Random();
81        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
82    }
83
84    @Override
85    public void onSetCamera(String cameraId) {
86        log("Set camera to " + cameraId);
87        mCameraId = cameraId;
88
89        stopCamera();
90        // Get the capabilities of the camera
91        setCameraCapabilities(mCameraId);
92    }
93
94    @Override
95    public void onSetPreviewSurface(Surface surface) {
96        log("Set preview surface " + (surface == null ? "unset" : "set"));
97        if (mPreviewSurface != null) {
98            stopCamera();
99        }
100
101        mPreviewSurface = surface;
102
103        if (!TextUtils.isEmpty(mCameraId) && mPreviewSurface != null) {
104            startCamera(mCameraId);
105        }
106    }
107
108    @Override
109    public void onSetDisplaySurface(Surface surface) {
110        log("Set display surface " + (surface == null ? "unset" : "set"));
111        mDisplaySurface = surface;
112
113        if (mDisplaySurface != null) {
114            if (mIncomingMediaPlayer == null) {
115                // For a Rick-Rolling good time use R.raw.test_video
116                mIncomingMediaPlayer = createMediaPlayer(mDisplaySurface, R.raw.test_pattern);
117            }
118            mIncomingMediaPlayer.setSurface(mDisplaySurface);
119            if (!mIncomingMediaPlayer.isPlaying()) {
120                mIncomingMediaPlayer.start();
121            }
122        } else {
123            if (mIncomingMediaPlayer != null) {
124                mIncomingMediaPlayer.stop();
125                mIncomingMediaPlayer.setSurface(null);
126            }
127        }
128    }
129
130    @Override
131    public void onSetDeviceOrientation(int rotation) {
132        log("Set device orientation " + rotation);
133    }
134
135    /**
136     * Sets the zoom value, creating a new CallCameraCapabalities object. If the zoom value is
137     * non-positive, assume that zoom is not supported.
138     */
139    @Override
140    public void onSetZoom(float value) {
141        log("Set zoom to " + value);
142    }
143
144    /**
145     * "Sends" a request with a video call profile. Assumes that this response succeeds and sends
146     * the response back via the CallVideoClient.
147     */
148    @Override
149    public void onSendSessionModifyRequest(final VideoProfile fromProfile,
150            final VideoProfile requestProfile) {
151        log("Sent session modify request");
152
153        mHandler.postDelayed(new Runnable() {
154            @Override
155            public void run() {
156                final VideoProfile responseProfile = new VideoProfile(
157                        requestProfile.getVideoState(), requestProfile.getQuality());
158                mConnection.setVideoState(requestProfile.getVideoState());
159
160                receiveSessionModifyResponse(
161                        SESSION_MODIFY_REQUEST_SUCCESS,
162                        requestProfile,
163                        responseProfile);
164            }
165        }, 2000);
166    }
167
168    @Override
169    public void onSendSessionModifyResponse(VideoProfile responseProfile) {
170
171    }
172
173    /**
174     * Returns a CallCameraCapabilities object without supporting zoom.
175     */
176    @Override
177    public void onRequestCameraCapabilities() {
178        log("Requested camera capabilities");
179        changeCameraCapabilities(mCameraCapabilities);
180    }
181
182    /**
183     * Randomly reports data usage of value ranging from 10MB to 60MB.
184     */
185    @Override
186    public void onRequestConnectionDataUsage() {
187        log("Requested connection data usage");
188        long dataUsageKb = (10 *1024) + random.nextInt(50 * 1024);
189        changeCallDataUsage(dataUsageKb);
190    }
191
192    /**
193     * We do not have a need to set a paused image.
194     */
195    @Override
196    public void onSetPauseImage(Uri uri) {
197        // Not implemented.
198    }
199
200    /**
201     * Stop and cleanup the media players used for test video playback.
202     */
203    public void stopAndCleanupMedia() {
204        if (mIncomingMediaPlayer != null) {
205            mIncomingMediaPlayer.setSurface(null);
206            mIncomingMediaPlayer.stop();
207            mIncomingMediaPlayer.release();
208            mIncomingMediaPlayer = null;
209        }
210    }
211
212    private static void log(String msg) {
213        Log.w("TestCallVideoProvider", "[TestCallServiceProvider] " + msg);
214    }
215
216    /**
217     * Creates a media player to play a video resource on a surface.
218     * @param surface The surface.
219     * @param videoResource The video resource.
220     * @return The {@code MediaPlayer}.
221     */
222    private MediaPlayer createMediaPlayer(Surface surface, int videoResource) {
223        MediaPlayer mediaPlayer = MediaPlayer.create(mContext.getApplicationContext(),
224                videoResource);
225        mediaPlayer.setSurface(surface);
226        mediaPlayer.setLooping(true);
227        return mediaPlayer;
228    }
229
230    /**
231     * Starts displaying the camera image on the preview surface.
232     *
233     * @param cameraId
234     */
235    private void startCamera(String cameraId) {
236        stopCamera();
237
238        if (mPreviewSurface == null) {
239            return;
240        }
241
242        // Configure a looper thread.
243        mLooperThread = new CameraThread();
244        Handler mHandler;
245        try {
246            mHandler = mLooperThread.start();
247        } catch (Exception e) {
248            log("Exception: " + e);
249            return;
250        }
251
252        // Get the camera device.
253        try {
254            BlockingCameraManager blockingCameraManager = new BlockingCameraManager(mCameraManager);
255            mCameraDevice = blockingCameraManager.openCamera(cameraId, null /* listener */,
256                    mHandler);
257        } catch (CameraAccessException e) {
258            log("CameraAccessException: " + e);
259            return;
260        } catch (BlockingOpenException be) {
261            log("BlockingOpenException: " + be);
262            return;
263        }
264
265        // Create a capture session to get the preview and display it on the surface.
266        List<Surface> surfaces = new ArrayList<Surface>();
267        surfaces.add(mPreviewSurface);
268        CaptureRequest.Builder mCaptureRequest = null;
269        try {
270            BlockingSessionCallback blkSession = new BlockingSessionCallback();
271            mCameraDevice.createCaptureSession(surfaces, blkSession, mHandler);
272            mCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
273            mCaptureRequest.addTarget(mPreviewSurface);
274            mCameraSession = blkSession.waitAndGetSession(SESSION_TIMEOUT_MS);
275        } catch (CameraAccessException e) {
276            log("CameraAccessException: " + e);
277            return;
278        }
279
280        // Keep repeating
281        try {
282            mCameraSession.setRepeatingRequest(mCaptureRequest.build(), new CameraCaptureCallback(),
283                    mHandler);
284        } catch (CameraAccessException e) {
285            log("CameraAccessException: " + e);
286            return;
287        }
288    }
289
290    /**
291     * Stops the camera and looper thread.
292     */
293    public void stopCamera() {
294        try {
295            if (mCameraDevice != null) {
296                mCameraDevice.close();
297                mCameraDevice = null;
298            }
299            if (mLooperThread != null) {
300                mLooperThread.close();
301                mLooperThread = null;
302            }
303        } catch (Exception e) {
304           log("stopCamera Exception: "+e.toString());
305        }
306    }
307
308    /**
309     * Required listener for camera capture events.
310     */
311    private class CameraCaptureCallback extends CameraCaptureSession.CaptureCallback {
312        @Override
313        public void onCaptureCompleted(CameraCaptureSession camera, CaptureRequest request,
314                TotalCaptureResult result) {
315        }
316
317        @Override
318        public void onCaptureFailed(CameraCaptureSession camera, CaptureRequest request,
319                CaptureFailure failure) {
320        }
321    }
322
323    /**
324     * Uses the camera manager to retrieve the camera capabilities for the chosen camera.
325     *
326     * @param cameraId The camera ID to get the capabilities for.
327     */
328    private void setCameraCapabilities(String cameraId) {
329        CameraManager cameraManager = (CameraManager) mContext.getSystemService(
330                Context.CAMERA_SERVICE);
331
332        CameraCharacteristics c = null;
333        try {
334            c = cameraManager.getCameraCharacteristics(cameraId);
335        } catch (IllegalArgumentException | CameraAccessException e) {
336            // Ignoring camera problems.
337        }
338        if (c != null) {
339            // Get the video size for the camera
340            StreamConfigurationMap map = c.get(
341                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
342            Size previewSize = map.getOutputSizes(SurfaceTexture.class)[0];
343
344            mCameraCapabilities = new CameraCapabilities(previewSize.getWidth(),
345                    previewSize.getHeight());
346        }
347    }
348}
349