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 @Override onHandoverFromWiFiToLte()114 public void onHandoverFromWiFiToLte() {} 115 116 /** 117 * Opens the first front facing camera on the device into a {@link SurfaceView} while preserving 118 * aspect ratio. 119 */ openCamera()120 private void openCamera() { 121 CameraManager manager = context.getSystemService(CameraManager.class); 122 123 StreamConfigurationMap configMap = getFrontFacingCameraSizes(manager); 124 if (configMap == null) { 125 return; 126 } 127 128 Size previewSize = getOptimalSize(configMap.getOutputSizes(SurfaceHolder.class)); 129 LogUtil.i("SelfManagedAnswerVideoCallScreen.openCamera", "Optimal size: " + previewSize); 130 float outputAspect = (float) previewSize.getWidth() / previewSize.getHeight(); 131 surfaceView.setAspectRatio(outputAspect); 132 surfaceView.getHolder().setFixedSize(previewSize.getWidth(), previewSize.getHeight()); 133 134 try { 135 manager.openCamera(cameraId, this, null); 136 } catch (CameraAccessException e) { 137 LogUtil.e("SelfManagedAnswerVideoCallScreen.openCamera", "failed to open camera", e); 138 } 139 } 140 141 @Nullable getFrontFacingCameraSizes(CameraManager manager)142 private StreamConfigurationMap getFrontFacingCameraSizes(CameraManager manager) { 143 String[] cameraIds; 144 try { 145 cameraIds = manager.getCameraIdList(); 146 } catch (CameraAccessException e) { 147 LogUtil.e( 148 "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes", 149 "failed to get camera ids", 150 e); 151 return null; 152 } 153 154 for (String cameraId : cameraIds) { 155 CameraCharacteristics characteristics; 156 try { 157 characteristics = manager.getCameraCharacteristics(cameraId); 158 } catch (CameraAccessException e) { 159 LogUtil.e( 160 "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes", 161 "failed to get camera characteristics", 162 e); 163 continue; 164 } 165 166 if (characteristics.get(CameraCharacteristics.LENS_FACING) 167 != CameraCharacteristics.LENS_FACING_FRONT) { 168 continue; 169 } 170 171 StreamConfigurationMap configMap = 172 characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 173 if (configMap == null) { 174 continue; 175 } 176 177 this.cameraId = cameraId; 178 return configMap; 179 } 180 LogUtil.e( 181 "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes", "No valid configurations."); 182 return null; 183 } 184 185 /** 186 * Given an array of {@link Size}s, tries to find the largest Size such that the aspect ratio of 187 * the returned size is within {@code ASPECT_TOLERANCE} of {@code TARGET_ASPECT}. This is useful 188 * because it provides us with an adequate size/camera resolution that will experience the least 189 * stretching from our fullscreen UI that doesn't match any of the camera sizes. 190 */ getOptimalSize(Size[] outputSizes)191 private static Size getOptimalSize(Size[] outputSizes) { 192 Size bestCandidateSize = outputSizes[0]; 193 float bestCandidateAspect = 194 (float) bestCandidateSize.getWidth() / bestCandidateSize.getHeight(); 195 196 for (Size candidateSize : outputSizes) { 197 if (candidateSize.getWidth() < MAX_WIDTH) { 198 float candidateAspect = (float) candidateSize.getWidth() / candidateSize.getHeight(); 199 boolean isGoodCandidateAspect = 200 Math.abs(candidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE; 201 boolean isGoodOutputAspect = 202 Math.abs(bestCandidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE; 203 204 if ((isGoodCandidateAspect && !isGoodOutputAspect) 205 || candidateSize.getWidth() > bestCandidateSize.getWidth()) { 206 bestCandidateSize = candidateSize; 207 bestCandidateAspect = candidateAspect; 208 } 209 } 210 } 211 return bestCandidateSize; 212 } 213 214 @Override 215 public void onOpened(CameraDevice camera) { 216 LogUtil.i("SelfManagedAnswerVideoCallScreen.opOpened", "camera opened."); 217 this.camera = camera; 218 Surface surface = surfaceView.getHolder().getSurface(); 219 try { 220 captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 221 captureRequestBuilder.addTarget(surface); 222 camera.createCaptureSession(Arrays.asList(surface), new CaptureSessionCallback(), null); 223 } catch (CameraAccessException e) { 224 LogUtil.e( 225 "SelfManagedAnswerVideoCallScreen.createCameraPreview", "failed to create preview", e); 226 } 227 } 228 229 @Override 230 public void onDisconnected(CameraDevice camera) { 231 closeCamera(); 232 } 233 234 @Override 235 public void onError(CameraDevice camera, int error) { 236 closeCamera(); 237 } 238 239 private void closeCamera() { 240 if (camera != null) { 241 camera.close(); 242 camera = null; 243 } 244 } 245 246 private class CaptureSessionCallback extends CameraCaptureSession.StateCallback { 247 248 @Override 249 public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { 250 LogUtil.i( 251 "SelfManagedAnswerVideoCallScreen.onConfigured", "camera capture session configured."); 252 // The camera is already closed. 253 if (camera == null) { 254 return; 255 } 256 257 // When the session is ready, we start displaying the preview. 258 captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); 259 try { 260 cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); 261 } catch (CameraAccessException e) { 262 LogUtil.e("CaptureSessionCallback.onConfigured", "failed to configure", e); 263 } 264 } 265 266 @Override 267 public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { 268 LogUtil.e("CaptureSessionCallback.onConfigureFailed", "failed to configure"); 269 } 270 } 271 } 272