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>>() {