Merge "Do not log a call that is cancelled before selecting an account" into lmp-dev
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/src/com/android/telecomm/PhoneAccountRegistrar.java b/src/com/android/telecomm/PhoneAccountRegistrar.java
index 7fd000a..9c45c24 100644
--- a/src/com/android/telecomm/PhoneAccountRegistrar.java
+++ b/src/com/android/telecomm/PhoneAccountRegistrar.java
@@ -56,7 +56,32 @@
public PhoneAccountHandle getDefaultOutgoingPhoneAccount() {
State s = read();
- return s.defaultOutgoingHandle;
+
+ if (s.defaultOutgoingHandle != null) {
+ // Return the registered outgoing default iff it still exists (we keep a sticky
+ // default to survive account deletion and re-addition)
+ for (int i = 0; i < s.accounts.size(); i++) {
+ if (s.accounts.get(i).getAccountHandle().equals(s.defaultOutgoingHandle)) {
+ return s.defaultOutgoingHandle;
+ }
+ }
+ // At this point, there was a registered default but it has been deleted; remember
+ // it for the future, but return null from this method
+ return null;
+ } else {
+ List<PhoneAccountHandle> enabled = getEnabledPhoneAccounts();
+ switch (enabled.size()) {
+ case 0:
+ // There are no accounts, so there can be no default
+ return null;
+ case 1:
+ // There is only one account, which is by definition the default
+ return enabled.get(0);
+ default:
+ // There are multiple accounts with no selected default
+ return null;
+ }
+ }
}
public void setDefaultOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
@@ -88,7 +113,7 @@
public List<PhoneAccountHandle> getEnabledPhoneAccounts() {
State s = read();
- return accountHandlesOnly(s);
+ return simSubscriptionAccountHandles(s);
}
public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
@@ -129,8 +154,6 @@
}
}
- checkDefaultOutgoing(s);
-
write(s);
}
@@ -145,26 +168,15 @@
}
}
- checkDefaultOutgoing(s);
-
write(s);
}
- private void checkDefaultOutgoing(State s) {
- // Check that, after an operation that removes accounts, the account set up as the "default
- // outgoing" has not been deleted. If it has, then clear out the setting.
- for (PhoneAccount m : s.accounts) {
- if (Objects.equals(s.defaultOutgoingHandle, m.getAccountHandle())) {
- return;
- }
- }
- s.defaultOutgoingHandle = null;
- }
-
- private List<PhoneAccountHandle> accountHandlesOnly(State s) {
+ private List<PhoneAccountHandle> simSubscriptionAccountHandles(State s) {
List<PhoneAccountHandle> accountHandles = new ArrayList<>();
for (PhoneAccount m : s.accounts) {
- accountHandles.add(m.getAccountHandle());
+ if ((m.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
+ accountHandles.add(m.getAccountHandle());
+ }
}
return accountHandles;
}
diff --git a/src/com/android/telecomm/TelecommServiceImpl.java b/src/com/android/telecomm/TelecommServiceImpl.java
index e08571a..b0fc709 100644
--- a/src/com/android/telecomm/TelecommServiceImpl.java
+++ b/src/com/android/telecomm/TelecommServiceImpl.java
@@ -44,6 +44,8 @@
* Implementation of the ITelecomm interface.
*/
public class TelecommServiceImpl extends ITelecommService.Stub {
+ private static final String TELEPHONY_PACKAGE_NAME = "com.android.phone";
+
/** ${inheritDoc} */
@Override
public IBinder asBinder() {
@@ -191,6 +193,9 @@
try {
enforceModifyPermissionOrCallingPackage(
account.getAccountHandle().getComponentName().getPackageName());
+ if ((account.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
+ enforceModifyPermissionOrCallingPackage(TELEPHONY_PACKAGE_NAME);
+ }
mPhoneAccountRegistrar.registerPhoneAccount(account);
} catch (Exception e) {
Log.e(this, e, "registerPhoneAccount %s", account);
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>>() {