• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.incallui.answer.impl;
18 
19 import android.content.Context;
20 import android.hardware.camera2.CameraAccessException;
21 import android.hardware.camera2.CameraCaptureSession;
22 import android.hardware.camera2.CameraCharacteristics;
23 import android.hardware.camera2.CameraDevice;
24 import android.hardware.camera2.CameraDevice.StateCallback;
25 import android.hardware.camera2.CameraManager;
26 import android.hardware.camera2.CameraMetadata;
27 import android.hardware.camera2.CaptureRequest;
28 import android.hardware.camera2.params.StreamConfigurationMap;
29 import android.support.annotation.NonNull;
30 import android.support.annotation.Nullable;
31 import android.support.v4.app.Fragment;
32 import android.util.Size;
33 import android.view.Surface;
34 import android.view.SurfaceHolder;
35 import android.view.SurfaceView;
36 import android.view.View;
37 import com.android.dialer.common.Assert;
38 import com.android.dialer.common.LogUtil;
39 import com.android.incallui.video.protocol.VideoCallScreen;
40 import java.util.Arrays;
41 
42 /**
43  * Shows the local preview for the incoming video call or video upgrade request. This class is used
44  * for RCS Video Share where we need to open the camera preview ourselves. For IMS Video the camera
45  * is managed by the modem, see {@link AnswerVideoCallScreen}.
46  */
47 public class SelfManagedAnswerVideoCallScreen extends StateCallback implements VideoCallScreen {
48 
49   private static final int MAX_WIDTH = 1920;
50   private static final float ASPECT_TOLERANCE = 0.1f;
51   private static final float TARGET_ASPECT = 16.f / 9.f;
52 
53   @NonNull private final String callId;
54   @NonNull private final Fragment fragment;
55   @NonNull private final FixedAspectSurfaceView surfaceView;
56   private final Context context;
57 
58   private String cameraId;
59   private CameraDevice camera;
60   private CaptureRequest.Builder captureRequestBuilder;
61 
SelfManagedAnswerVideoCallScreen( @onNull String callId, @NonNull Fragment fragment, @NonNull View view)62   public SelfManagedAnswerVideoCallScreen(
63       @NonNull String callId, @NonNull Fragment fragment, @NonNull View view) {
64     this.callId = Assert.isNotNull(callId);
65     this.fragment = Assert.isNotNull(fragment);
66     this.context = Assert.isNotNull(fragment.getContext());
67 
68     surfaceView =
69         Assert.isNotNull(
70             (FixedAspectSurfaceView) view.findViewById(R.id.incoming_preview_surface_view));
71     surfaceView.setVisibility(View.VISIBLE);
72     view.findViewById(R.id.incoming_preview_texture_view_overlay).setVisibility(View.VISIBLE);
73     view.setBackgroundColor(0xff000000);
74   }
75 
76   @Override
onVideoScreenStart()77   public void onVideoScreenStart() {
78     openCamera();
79   }
80 
81   @Override
onVideoScreenStop()82   public void onVideoScreenStop() {
83     closeCamera();
84   }
85 
86   @Override
showVideoViews( boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld)87   public void showVideoViews(
88       boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld) {}
89 
90   @Override
onLocalVideoDimensionsChanged()91   public void onLocalVideoDimensionsChanged() {}
92 
93   @Override
onLocalVideoOrientationChanged()94   public void onLocalVideoOrientationChanged() {}
95 
96   @Override
onRemoteVideoDimensionsChanged()97   public void onRemoteVideoDimensionsChanged() {}
98 
99   @Override
updateFullscreenAndGreenScreenMode( boolean shouldShowFullscreen, boolean shouldShowGreenScreen)100   public void updateFullscreenAndGreenScreenMode(
101       boolean shouldShowFullscreen, boolean shouldShowGreenScreen) {}
102 
103   @Override
getVideoCallScreenFragment()104   public Fragment getVideoCallScreenFragment() {
105     return fragment;
106   }
107 
108   @Override
getCallId()109   public String getCallId() {
110     return callId;
111   }
112 
113   /**
114    * Opens the first front facing camera on the device into a {@link SurfaceView} while preserving
115    * aspect ratio.
116    */
openCamera()117   private void openCamera() {
118     CameraManager manager = context.getSystemService(CameraManager.class);
119 
120     StreamConfigurationMap configMap = getFrontFacingCameraSizes(manager);
121     if (configMap == null) {
122       return;
123     }
124 
125     Size previewSize = getOptimalSize(configMap.getOutputSizes(SurfaceHolder.class));
126     LogUtil.i("SelfManagedAnswerVideoCallScreen.openCamera", "Optimal size: " + previewSize);
127     float outputAspect = (float) previewSize.getWidth() / previewSize.getHeight();
128     surfaceView.setAspectRatio(outputAspect);
129     surfaceView.getHolder().setFixedSize(previewSize.getWidth(), previewSize.getHeight());
130 
131     try {
132       manager.openCamera(cameraId, this, null);
133     } catch (CameraAccessException e) {
134       LogUtil.e("SelfManagedAnswerVideoCallScreen.openCamera", "failed to open camera", e);
135     }
136   }
137 
138   @Nullable
getFrontFacingCameraSizes(CameraManager manager)139   private StreamConfigurationMap getFrontFacingCameraSizes(CameraManager manager) {
140     String[] cameraIds;
141     try {
142       cameraIds = manager.getCameraIdList();
143     } catch (CameraAccessException e) {
144       LogUtil.e(
145           "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes",
146           "failed to get camera ids",
147           e);
148       return null;
149     }
150 
151     for (String cameraId : cameraIds) {
152       CameraCharacteristics characteristics;
153       try {
154         characteristics = manager.getCameraCharacteristics(cameraId);
155       } catch (CameraAccessException e) {
156         LogUtil.e(
157             "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes",
158             "failed to get camera characteristics",
159             e);
160         continue;
161       }
162 
163       if (characteristics.get(CameraCharacteristics.LENS_FACING)
164           != CameraCharacteristics.LENS_FACING_FRONT) {
165         continue;
166       }
167 
168       StreamConfigurationMap configMap =
169           characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
170       if (configMap == null) {
171         continue;
172       }
173 
174       this.cameraId = cameraId;
175       return configMap;
176     }
177     LogUtil.e(
178         "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes", "No valid configurations.");
179     return null;
180   }
181 
182   /**
183    * Given an array of {@link Size}s, tries to find the largest Size such that the aspect ratio of
184    * the returned size is within {@code ASPECT_TOLERANCE} of {@code TARGET_ASPECT}. This is useful
185    * because it provides us with an adequate size/camera resolution that will experience the least
186    * stretching from our fullscreen UI that doesn't match any of the camera sizes.
187    */
getOptimalSize(Size[] outputSizes)188   private static Size getOptimalSize(Size[] outputSizes) {
189     Size bestCandidateSize = outputSizes[0];
190     float bestCandidateAspect =
191         (float) bestCandidateSize.getWidth() / bestCandidateSize.getHeight();
192 
193     for (Size candidateSize : outputSizes) {
194       if (candidateSize.getWidth() < MAX_WIDTH) {
195         float candidateAspect = (float) candidateSize.getWidth() / candidateSize.getHeight();
196         boolean isGoodCandidateAspect =
197             Math.abs(candidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
198         boolean isGoodOutputAspect =
199             Math.abs(bestCandidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
200 
201         if ((isGoodCandidateAspect && !isGoodOutputAspect)
202             || candidateSize.getWidth() > bestCandidateSize.getWidth()) {
203           bestCandidateSize = candidateSize;
204           bestCandidateAspect = candidateAspect;
205         }
206       }
207     }
208     return bestCandidateSize;
209   }
210 
211   @Override
212   public void onOpened(CameraDevice camera) {
213     LogUtil.i("SelfManagedAnswerVideoCallScreen.opOpened", "camera opened.");
214     this.camera = camera;
215     Surface surface = surfaceView.getHolder().getSurface();
216     try {
217       captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
218       captureRequestBuilder.addTarget(surface);
219       camera.createCaptureSession(Arrays.asList(surface), new CaptureSessionCallback(), null);
220     } catch (CameraAccessException e) {
221       LogUtil.e(
222           "SelfManagedAnswerVideoCallScreen.createCameraPreview", "failed to create preview", e);
223     }
224   }
225 
226   @Override
227   public void onDisconnected(CameraDevice camera) {
228     closeCamera();
229   }
230 
231   @Override
232   public void onError(CameraDevice camera, int error) {
233     closeCamera();
234   }
235 
236   private void closeCamera() {
237     if (camera != null) {
238       camera.close();
239       camera = null;
240     }
241   }
242 
243   private class CaptureSessionCallback extends CameraCaptureSession.StateCallback {
244 
245     @Override
246     public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
247       LogUtil.i(
248           "SelfManagedAnswerVideoCallScreen.onConfigured", "camera capture session configured.");
249       // The camera is already closed.
250       if (camera == null) {
251         return;
252       }
253 
254       // When the session is ready, we start displaying the preview.
255       captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
256       try {
257         cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
258       } catch (CameraAccessException e) {
259         LogUtil.e("CaptureSessionCallback.onConfigured", "failed to configure", e);
260       }
261     }
262 
263     @Override
264     public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
265       LogUtil.e("CaptureSessionCallback.onConfigureFailed", "failed to configure");
266     }
267   }
268 }
269