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 com.android.server.telecom.testapps;
18 
19 import com.android.ex.camera2.blocking.BlockingCameraManager;
20 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
21 import com.android.ex.camera2.blocking.BlockingSessionCallback;
22 import com.android.server.telecom.testapps.R;
23 import com.android.server.telecom.testapps.TestConnectionService.TestConnection;
24 
25 import android.content.Context;
26 import android.graphics.SurfaceTexture;
27 import android.hardware.camera2.CameraAccessException;
28 import android.hardware.camera2.CameraCaptureSession;
29 import android.hardware.camera2.CameraCharacteristics;
30 import android.hardware.camera2.CameraDevice;
31 import android.hardware.camera2.CameraManager;
32 import android.hardware.camera2.CaptureFailure;
33 import android.hardware.camera2.CaptureRequest;
34 import android.hardware.camera2.TotalCaptureResult;
35 import android.hardware.camera2.params.StreamConfigurationMap;
36 import android.media.MediaPlayer;
37 import android.net.Uri;
38 import android.os.Handler;
39 import android.telecom.Connection;
40 import android.telecom.VideoProfile;
41 import android.telecom.VideoProfile.CameraCapabilities;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.util.Size;
45 import android.view.Surface;
46 
47 import java.lang.IllegalArgumentException;
48 import java.lang.String;
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Random;
52 
53 /**
54  * Implements the VideoCallProvider.
55  */
56 public 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 
TestVideoProvider(Context context, TestConnection connection)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
onSetCamera(String cameraId)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
onSetPreviewSurface(Surface surface)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
onSetDisplaySurface(Surface surface)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
onSetDeviceOrientation(int rotation)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
onSetZoom(float value)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
onSendSessionModifyRequest(final VideoProfile fromProfile, final VideoProfile requestProfile)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
onSendSessionModifyResponse(VideoProfile responseProfile)169     public void onSendSessionModifyResponse(VideoProfile responseProfile) {
170 
171     }
172 
173     /**
174      * Returns a CallCameraCapabilities object without supporting zoom.
175      */
176     @Override
onRequestCameraCapabilities()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
onRequestConnectionDataUsage()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
onSetPauseImage(Uri uri)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      */
stopAndCleanupMedia()203     public void stopAndCleanupMedia() {
204         if (mIncomingMediaPlayer != null) {
205             mIncomingMediaPlayer.setSurface(null);
206             mIncomingMediaPlayer.stop();
207             mIncomingMediaPlayer.release();
208             mIncomingMediaPlayer = null;
209         }
210     }
211 
log(String msg)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      */
createMediaPlayer(Surface surface, int videoResource)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      */
startCamera(String cameraId)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      */
stopCamera()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
onCaptureCompleted(CameraCaptureSession camera, CaptureRequest request, TotalCaptureResult result)313         public void onCaptureCompleted(CameraCaptureSession camera, CaptureRequest request,
314                 TotalCaptureResult result) {
315         }
316 
317         @Override
onCaptureFailed(CameraCaptureSession camera, CaptureRequest request, CaptureFailure failure)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      */
setCameraCapabilities(String cameraId)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