TestCallVideoProvider enhancements, telecomm Call fix.
1. Displaying actual camera preview on preview surface for Test
Call Video Provider.
2. In telecomm Call, setting the video state during connection creation
so that the incoming call video state is properly populated.
Bug: 16043064
Change-Id: I77d0d673ad79376202f09310f9202bd9a7363e03
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 27ed0d7..c5c0487 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -578,6 +578,7 @@
setCallerDisplayName(
connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
setCallVideoProvider(connection.getCallVideoProvider());
+ setVideoState(connection.getVideoState());
if (mIsIncoming) {
// We do not handle incoming calls immediately when they are verified by the connection
diff --git a/tests/Android.mk b/tests/Android.mk
index 592d408..e4d00f9 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -18,6 +18,7 @@
include $(CLEAR_VARS)
LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-ex-camera2 \
guava \
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index a7b34f2..fecfffb 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -17,6 +17,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.telecomm.tests">
+ <!-- Test connection service outgoing video preview. -->
+ <uses-permission android:name="android.permission.CAMERA" />
+
<application android:label="@string/app_name">
<uses-library android:name="android.test.runner" />
diff --git a/tests/src/com/android/telecomm/testapps/CameraThread.java b/tests/src/com/android/telecomm/testapps/CameraThread.java
new file mode 100644
index 0000000..f077428
--- /dev/null
+++ b/tests/src/com/android/telecomm/testapps/CameraThread.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.telecomm.testapps;
+
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import java.lang.AutoCloseable;
+import java.lang.Exception;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.Thread;
+import java.lang.Throwable;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Camera thread class used for handling camera callbacks.
+ */
+public class CameraThread implements AutoCloseable {
+ private static final String TAG = "CameraThread";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ // Timeout for initializing looper and opening camera in Milliseconds.
+ private static final long WAIT_FOR_COMMAND_TO_COMPLETE = 5000;
+ private Looper mLooper = null;
+ private Handler mHandler = null;
+
+ /**
+ * Create and start a looper thread, return the Handler
+ */
+ public synchronized Handler start() throws Exception {
+ final ConditionVariable startDone = new ConditionVariable();
+ if (mHandler != null) {
+ Log.w(TAG, "Looper thread already started");
+ return mHandler;
+ }
+
+ new Thread() {
+ @Override
+ public void run() {
+ if (VERBOSE) Log.v(TAG, "start loopRun");
+ Looper.prepare();
+ // Save the looper so that we can terminate this thread
+ // after we are done with it.
+ mLooper = Looper.myLooper();
+ mHandler = new Handler();
+ startDone.open();
+ Looper.loop();
+ if (VERBOSE) Log.v(TAG, "createLooperThread: finished");
+ }
+ }.start();
+
+ if (VERBOSE) Log.v(TAG, "start waiting for looper");
+ if (!startDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
+ throw new TimeoutException("createLooperThread: start timeout");
+ }
+ return mHandler;
+ }
+
+ /**
+ * Terminate the looper thread
+ */
+ public synchronized void close() throws Exception {
+ if (mLooper == null || mHandler == null) {
+ Log.w(TAG, "Looper thread doesn't start yet");
+ return;
+ }
+
+ if (VERBOSE) Log.v(TAG, "Terminate looper thread");
+ mLooper.quit();
+ mLooper.getThread().join();
+ mLooper = null;
+ mHandler = null;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/tests/src/com/android/telecomm/testapps/TestCallVideoProvider.java b/tests/src/com/android/telecomm/testapps/TestCallVideoProvider.java
index 195bf6d..3f83bb9 100644
--- a/tests/src/com/android/telecomm/testapps/TestCallVideoProvider.java
+++ b/tests/src/com/android/telecomm/testapps/TestCallVideoProvider.java
@@ -16,21 +16,35 @@
package com.android.telecomm.testapps;
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingSessionListener;
+import com.android.telecomm.tests.R;
+
import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.RemoteException;
import android.telecomm.CallCameraCapabilities;
import android.telecomm.CallVideoClient;
import android.telecomm.CallVideoProvider;
import android.telecomm.RemoteCallVideoClient;
import android.telecomm.VideoCallProfile;
-
+import android.text.TextUtils;
import android.util.Log;
import android.view.Surface;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
-import com.android.telecomm.tests.R;
-
/**
* Implements the CallVideoProvider.
*/
@@ -43,13 +57,21 @@
private Context mContext;
/** Used to play incoming video during a call. */
private MediaPlayer mIncomingMediaPlayer;
- /** Used to play outgoing video during a call. */
- private MediaPlayer mOutgoingMediaPlayer;
+
+ private CameraManager mCameraManager;
+ private CameraDevice mCameraDevice;
+ private CameraCaptureSession mCameraSession;
+ private CameraThread mLooperThread;
+
+ private String mCameraId;
+
+ private static final long SESSION_TIMEOUT_MS = 2000;
public TestCallVideoProvider(Context context) {
mContext = context;
mCapabilities = new CallCameraCapabilities(false /* zoomSupported */, 0 /* maxZoom */);
random = new Random();
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
}
/**
@@ -63,6 +85,10 @@
@Override
public void onSetCamera(String cameraId) {
log("Set camera to " + cameraId);
+ mCameraId = cameraId;
+ if (mPreviewSurface != null && mCameraId != null) {
+ startCamera(cameraId);
+ }
}
@Override
@@ -70,17 +96,12 @@
log("Set preview surface " + (surface == null ? "unset" : "set"));
mPreviewSurface = surface;
- if (mPreviewSurface != null) {
- if (mOutgoingMediaPlayer == null) {
- mOutgoingMediaPlayer = createMediaPlayer(mPreviewSurface, R.raw.outgoing_video);
- }
- mOutgoingMediaPlayer.setSurface(mPreviewSurface);
- if (!mOutgoingMediaPlayer.isPlaying()) {
- mOutgoingMediaPlayer.start();
- }
- } else {
- mOutgoingMediaPlayer.stop();
- mOutgoingMediaPlayer.setSurface(null);
+ if (mPreviewSurface == null) {
+ stopCamera();
+ }
+
+ if (!TextUtils.isEmpty(mCameraId) && mPreviewSurface != null) {
+ startCamera(mCameraId);
}
}
@@ -98,8 +119,10 @@
mIncomingMediaPlayer.start();
}
} else {
- mIncomingMediaPlayer.stop();
- mIncomingMediaPlayer.setSurface(null);
+ if (mIncomingMediaPlayer != null) {
+ mIncomingMediaPlayer.stop();
+ mIncomingMediaPlayer.setSurface(null);
+ }
}
}
@@ -173,6 +196,18 @@
// Not implemented.
}
+ /**
+ * Stop and cleanup the media players used for test video playback.
+ */
+ public void stopAndCleanupMedia() {
+ if (mIncomingMediaPlayer != null) {
+ mIncomingMediaPlayer.setSurface(null);
+ mIncomingMediaPlayer.stop();
+ mIncomingMediaPlayer.release();
+ mIncomingMediaPlayer = null;
+ }
+ }
+
private static void log(String msg) {
Log.w("TestCallVideoProvider", "[TestCallServiceProvider] " + msg);
}
@@ -190,4 +225,97 @@
mediaPlayer.setLooping(true);
return mediaPlayer;
}
+
+ /**
+ * Starts displaying the camera image on the preview surface.
+ *
+ * @param cameraId
+ */
+ private void startCamera(String cameraId) {
+ stopCamera();
+
+ if (mPreviewSurface == null) {
+ return;
+ }
+
+ // Configure a looper thread.
+ mLooperThread = new CameraThread();
+ Handler mHandler;
+ try {
+ mHandler = mLooperThread.start();
+ } catch (Exception e) {
+ log("Exception: " + e);
+ return;
+ }
+
+ // Get the camera device.
+ try {
+ BlockingCameraManager blockingCameraManager = new BlockingCameraManager(mCameraManager);
+ mCameraDevice = blockingCameraManager.openCamera(cameraId, null /* listener */,
+ mHandler);
+ } catch (CameraAccessException e) {
+ log("CameraAccessException: " + e);
+ return;
+ } catch (BlockingOpenException be) {
+ log("BlockingOpenException: " + be);
+ return;
+ }
+
+ // Create a capture session to get the preview and display it on the surface.
+ List<Surface> surfaces = new ArrayList<Surface>();
+ surfaces.add(mPreviewSurface);
+ CaptureRequest.Builder mCaptureRequest = null;
+ try {
+ BlockingSessionListener blkSession = new BlockingSessionListener();
+ mCameraDevice.createCaptureSession(surfaces, blkSession, mHandler);
+ mCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ mCaptureRequest.addTarget(mPreviewSurface);
+ mCameraSession = blkSession.waitAndGetSession(SESSION_TIMEOUT_MS);
+ } catch (CameraAccessException e) {
+ log("CameraAccessException: " + e);
+ return;
+ }
+
+ // Keep repeating
+ try {
+ mCameraSession.setRepeatingRequest(mCaptureRequest.build(), new CameraCaptureListener(),
+ mHandler);
+ } catch (CameraAccessException e) {
+ log("CameraAccessException: " + e);
+ return;
+ }
+ }
+
+ /**
+ * Stops the camera and looper thread.
+ */
+ public void stopCamera() {
+ try {
+ if (mCameraDevice != null) {
+ mCameraDevice.close();
+ mCameraDevice = null;
+ }
+ if (mLooperThread != null) {
+ mLooperThread.close();
+ mLooperThread = null;
+ }
+ } catch (Exception e) {
+ log("stopCamera Exception: "+e.toString());
+ }
+ }
+
+ /**
+ * Required listener for camera capture events.
+ */
+ private class CameraCaptureListener extends CameraCaptureSession.CaptureListener {
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession camera, CaptureRequest request,
+ TotalCaptureResult result) {
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession camera, CaptureRequest request,
+ CaptureFailure failure) {
+ }
+ }
}
diff --git a/tests/src/com/android/telecomm/testapps/TestConnectionService.java b/tests/src/com/android/telecomm/testapps/TestConnectionService.java
index 6d232e9..803c380 100644
--- a/tests/src/com/android/telecomm/testapps/TestConnectionService.java
+++ b/tests/src/com/android/telecomm/testapps/TestConnectionService.java
@@ -24,6 +24,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.telecomm.CallAudioState;
+import android.telecomm.CallCapabilities;
+import android.telecomm.CallPropertyPresentation;
import android.telecomm.Connection;
import android.telecomm.ConnectionRequest;
import android.telecomm.ConnectionService;
@@ -134,6 +136,9 @@
private final RemoteConnection mRemoteConnection;
+ /** Used to cleanup camera and media when done with connection. */
+ private TestCallVideoProvider mTestCallVideoProvider;
+
TestConnection(RemoteConnection remoteConnection, int initialState) {
mRemoteConnection = remoteConnection;
if (mRemoteConnection != null) {
@@ -175,6 +180,7 @@
if (mRemoteConnection != null) {
mRemoteConnection.answer(videoState);
} else {
+ setVideoState(videoState);
activateCall(this);
setActive();
}
@@ -248,6 +254,20 @@
break;
}
}
+
+ public void setTestCallVideoProvider(TestCallVideoProvider testCallVideoProvider) {
+ mTestCallVideoProvider = testCallVideoProvider;
+ }
+
+ /**
+ * Stops playback of test videos.
+ */
+ private void stopAndCleanupMedia() {
+ if (mTestCallVideoProvider != null) {
+ mTestCallVideoProvider.stopAndCleanupMedia();
+ mTestCallVideoProvider.stopCamera();
+ }
+ }
}
private class CallAttempter implements CreateConnectionResponse<RemoteConnection> {
@@ -433,6 +453,7 @@
@Override
public void onCreateIncomingConnection(
final ConnectionRequest request, final CreateConnectionResponse<Connection> response) {
+
PhoneAccountHandle accountHandle = request.getAccountHandle();
ComponentName componentName = new ComponentName(this, TestConnectionService.class);
if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
@@ -445,11 +466,25 @@
TestConnection connection = new TestConnection(null, Connection.State.RINGING);
if (isVideoCall) {
- connection.setCallVideoProvider(new TestCallVideoProvider(getApplicationContext()));
+ TestCallVideoProvider testCallVideoProvider =
+ new TestCallVideoProvider(getApplicationContext());
+ connection.setCallVideoProvider(testCallVideoProvider);
+
+ // Keep reference to original so we can clean up the media players later.
+ connection.setTestCallVideoProvider(testCallVideoProvider);
}
+
+ // Assume all calls are video capable.
+ int capabilities = connection.getCallCapabilities();
+ capabilities |= CallCapabilities.SUPPORTS_VT_LOCAL;
+ connection.setCallCapabilities(capabilities);
+
int videoState = isVideoCall ?
VideoCallProfile.VIDEO_STATE_BIDIRECTIONAL :
VideoCallProfile.VIDEO_STATE_AUDIO_ONLY;
+ connection.setVideoState(videoState);
+ connection.setHandle(handle, CallPropertyPresentation.ALLOWED);
+
mCalls.add(connection);
ConnectionRequest newRequest = new ConnectionRequest(
@@ -460,7 +495,7 @@
request.getExtras(),
videoState);
response.onSuccess(newRequest, connection);
- connection.setVideoState(videoState);
+
} else {
SimpleResponse<Uri, List<PhoneAccountHandle>> accountResponse =
new SimpleResponse<Uri, List<PhoneAccountHandle>>() {