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