Merge "Notify BluetoothRouteManager when Bluetooth turned off"
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 61dd99c..4b86738 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -2008,7 +2008,8 @@
 
         if (videoProvider != null ) {
             try {
-                mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this);
+                mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this,
+                        mCallsManager);
             } catch (RemoteException ignored) {
                 // Ignore RemoteException.
             }
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 15a4f58..c9569d7 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -146,11 +146,13 @@
         // 2) It is a conference call
         // 3) Call was not explicitly canceled
         // 4) Call is not an external call
+        // 5) Call is not a self-managed call
         if (isNewlyDisconnected &&
                 (oldState != CallState.SELECT_PHONE_ACCOUNT &&
                  !call.isConference() &&
                  !isCallCanceled) &&
-                !call.isExternalCall()) {
+                !call.isExternalCall() &&
+                !call.isSelfManaged()) {
             int type;
             if (!call.isIncoming()) {
                 type = Calls.OUTGOING_TYPE;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 356666f..895e848 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -81,6 +81,8 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
@@ -94,7 +96,7 @@
  */
 @VisibleForTesting
 public class CallsManager extends Call.ListenerBase
-        implements VideoProviderProxy.Listener, CallFilterResultCallback {
+        implements VideoProviderProxy.Listener, CallFilterResultCallback, CurrentUserProxy {
 
     // TODO: Consider renaming this CallsManagerPlugin.
     @VisibleForTesting
@@ -121,6 +123,7 @@
 
     private static final String TAG = "CallsManager";
 
+    private static final int HANDLER_WAIT_TIMEOUT = 10000;
     private static final int MAXIMUM_LIVE_CALLS = 1;
     private static final int MAXIMUM_HOLD_CALLS = 1;
     private static final int MAXIMUM_RINGING_CALLS = 1;
@@ -612,6 +615,7 @@
         return mCallAudioManager.getForegroundCall();
     }
 
+    @Override
     public UserHandle getCurrentUserHandle() {
         return mCurrentUserHandle;
     }
@@ -2236,7 +2240,8 @@
      * Callback when foreground user is switched. We will reload missed call in all profiles
      * including the user itself. There may be chances that profiles are not started yet.
      */
-    void onUserSwitch(UserHandle userHandle) {
+    @VisibleForTesting
+    public void onUserSwitch(UserHandle userHandle) {
         mCurrentUserHandle = userHandle;
         mMissedCallNotifier.setCurrentUserHandle(userHandle);
         final UserManager userManager = UserManager.get(mContext);
@@ -2326,6 +2331,28 @@
     }
 
     /**
+     * Blocks execution until all Telecom handlers have completed their current work.
+     */
+    public void waitOnHandlers() {
+        CountDownLatch mainHandlerLatch = new CountDownLatch(3);
+        mHandler.post(() -> {
+            mainHandlerLatch.countDown();
+        });
+        mCallAudioManager.getCallAudioModeStateMachine().getHandler().post(() -> {
+            mainHandlerLatch.countDown();
+        });
+        mCallAudioManager.getCallAudioRouteStateMachine().getHandler().post(() -> {
+            mainHandlerLatch.countDown();
+        });
+
+        try {
+            mainHandlerLatch.await(HANDLER_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.w(this, "waitOnHandlers: interrupted %s", e);
+        }
+    }
+
+    /**
      * Dumps the state of the {@link CallsManager}.
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
diff --git a/src/com/android/server/telecom/CurrentUserProxy.java b/src/com/android/server/telecom/CurrentUserProxy.java
new file mode 100644
index 0000000..61dce61
--- /dev/null
+++ b/src/com/android/server/telecom/CurrentUserProxy.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 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.server.telecom;
+
+import android.os.UserHandle;
+
+/**
+ * Defines common functionality for a class which has knowledge of the currently logged in user.
+ * Implemented by {@link CallsManager} and used by {@link VideoProviderProxy} so that it does not
+ * have a dependency on the entire CallsManager when all that is required is the ability to find out
+ * the handle of the currently logged in user.
+ */
+public interface CurrentUserProxy {
+    UserHandle getCurrentUserHandle();
+}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 4e5002c..51f11d7 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -354,6 +354,15 @@
                                 account.getAccountHandle().getComponentName().getPackageName());
                         if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
                             enforceRegisterSelfManaged();
+                            if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) ||
+                                    account.hasCapabilities(
+                                            PhoneAccount.CAPABILITY_CONNECTION_MANAGER) ||
+                                    account.hasCapabilities(
+                                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+                                throw new SecurityException("Self-managed ConnectionServices " +
+                                        "cannot also be call capable, connection managers, or " +
+                                        "SIM accounts.");
+                            }
                         }
                         if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
                             enforceRegisterSimSubscriptionPermission();
@@ -1243,6 +1252,30 @@
                 Log.endSession();
             }
         }
+
+        /**
+         * Blocks until all Telecom handlers have completed their current work.
+         *
+         * See {@link com.android.commands.telecom.Telecom}.
+         */
+        @Override
+        public void waitOnHandlers() {
+            try {
+                Log.startSession("TSI.wOH");
+                enforceModifyPermission();
+                synchronized (mLock) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        Log.i(this, "waitOnHandlers");
+                        mCallsManager.waitOnHandlers();
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
     };
 
     private Context mContext;
@@ -1474,7 +1507,7 @@
         if (phoneAccountHandle != null) {
                 PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
                         phoneAccountHandle);
-                return phoneAccount.isSelfManaged();
+                return phoneAccount != null && phoneAccount.isSelfManaged();
         }
         return false;
     }
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index b537df9..480d8c9 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.telecom.Connection;
 import android.telecom.InCallService;
 import android.telecom.Log;
@@ -88,6 +89,11 @@
      */
     private Call mCall;
 
+    /**
+     * Interface providing access to the currently logged in user.
+     */
+    private CurrentUserProxy mCurrentUserProxy;
+
     private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
         @Override
         public void binderDied() {
@@ -106,7 +112,8 @@
      * @throws RemoteException Remote exception.
      */
     VideoProviderProxy(TelecomSystem.SyncRoot lock,
-            IVideoProvider videoProvider, Call call) throws RemoteException {
+            IVideoProvider videoProvider, Call call, CurrentUserProxy currentUserProxy)
+            throws RemoteException {
 
         super(Looper.getMainLooper());
 
@@ -118,6 +125,7 @@
         mVideoCallListenerBinder = new VideoCallListenerBinder();
         mConectionServiceVideoProvider.addVideoCallback(mVideoCallListenerBinder);
         mCall = call;
+        mCurrentUserProxy = currentUserProxy;
     }
 
     /**
@@ -300,13 +308,15 @@
     public void onSetCamera(String cameraId, String callingPackage, int callingUid,
             int callingPid) {
         synchronized (mLock) {
-            logFromInCall("setCamera: " + cameraId + " callingPackage=" + callingPackage);
+            logFromInCall("setCamera: " + cameraId + " callingPackage=" + callingPackage +
+                    "; callingUid=" + callingUid);
 
             if (!TextUtils.isEmpty(cameraId)) {
                 if (!canUseCamera(mCall.getContext(), callingPackage, callingUid, callingPid)) {
                     // Calling app is not permitted to use the camera.  Ignore the request and send
                     // back a call session event indicating the error.
-                    Log.i(this, "onSetCamera: camera permission denied; package=%d, uid=%d, pid=%d",
+                    Log.i(this, "onSetCamera: camera permission denied; package=%d, uid=%d, "
+                            + "pid=%d",
                             callingPackage, callingUid, callingPid);
                     VideoProviderProxy.this.handleCallSessionEvent(
                             Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR);
@@ -528,11 +538,20 @@
      * @param context The context.
      * @param callingPackage The package name of the caller (i.e. Dialer).
      * @param callingUid The UID of the caller.
+     * @param callingPid The PID of the caller.
      * @return {@code true} if the calling uid and package can use the camera, {@code false}
      *      otherwise.
      */
     private boolean canUseCamera(Context context, String callingPackage, int callingUid,
             int callingPid) {
+
+        UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+        UserHandle currentUserHandle = mCurrentUserProxy.getCurrentUserHandle();
+        if (currentUserHandle != null && !currentUserHandle.equals(callingUser)) {
+            Log.w(this, "canUseCamera attempt to user camera by background user.");
+            return false;
+        }
+
         try {
             context.enforcePermission(Manifest.permission.CAMERA, callingPid, callingUid,
                     "Camera permission required.");
@@ -548,7 +567,7 @@
             return appOpsManager != null && appOpsManager.noteOp(AppOpsManager.OP_CAMERA,
                     callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED;
         } catch (SecurityException se) {
-            Log.w(this, "canUserCamera got appOpps Exception " + se.toString());
+            Log.w(this, "canUseCamera got appOpps Exception " + se.toString());
             return false;
         }
     }
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index da0f9b1..ee4890f 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -29,6 +29,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.telecom.Connection.VideoProvider;
 import android.telecom.InCallService;
 import android.telecom.InCallService.VideoCall;
@@ -211,6 +212,31 @@
     }
 
     /**
+     * Tests the caller user handle check in {@link VideoCall#setCamera(String)} to ensure a camera
+     * change from a background user is not permitted.
+     */
+    @MediumTest
+    public void testCameraChangeUserFail() throws Exception {
+        // Wait until the callback has been received before performing verification.
+        doAnswer(mVerification).when(mVideoCallCallback).onCallSessionEvent(anyInt());
+
+        // Set a fake user to be the current foreground user.
+        mTelecomSystem.getCallsManager().onUserSwitch(new UserHandle(1000));
+
+        // Make a request to change the camera
+        mVideoCall.setCamera(MockVideoProvider.CAMERA_FRONT);
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        // Capture the session event reported via the callback.
+        ArgumentCaptor<Integer> sessionEventCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT)).onCallSessionEvent(
+                sessionEventCaptor.capture());
+
+        assertEquals(VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR,
+                sessionEventCaptor.getValue().intValue());
+    }
+
+    /**
      * Tests the caller permission check in {@link VideoCall#setCamera(String)} to ensure the
      * caller can null out the camera, even if they do not have camera permission.
      */