Merge "Add ability to add sim-initiated MO call to UI (3/4)" into lmp-dev
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index ca72ec8..9b6ece1 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -70,6 +70,8 @@
         void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
         void onSuccessfulIncomingCall(Call call);
         void onFailedIncomingCall(Call call);
+        void onSuccessfulUnknownCall(Call call, int callState);
+        void onFailedUnknownCall(Call call);
         void onRingbackRequested(Call call, boolean ringbackRequested);
         void onPostDialWait(Call call, String remaining);
         void onCallCapabilitiesChanged(Call call);
@@ -99,6 +101,10 @@
         @Override
         public void onFailedIncomingCall(Call call) {}
         @Override
+        public void onSuccessfulUnknownCall(Call call, int callState) {}
+        @Override
+        public void onFailedUnknownCall(Call call) {}
+        @Override
         public void onRingbackRequested(Call call, boolean ringbackRequested) {}
         @Override
         public void onPostDialWait(Call call, String remaining) {}
@@ -167,6 +173,11 @@
     /** True if this is an incoming call. */
     private final boolean mIsIncoming;
 
+    /** True if this is a currently unknown call that was not previously tracked by CallsManager,
+     *  and did not originate via the regular incoming/outgoing call code paths.
+     */
+    private boolean mIsUnknown;
+
     /**
      * The time this call was created. Beyond logging and such, may also be used for bookkeeping
      * and specifically for marking certain call attempts as failed attempts.
@@ -660,7 +671,11 @@
             mConferenceableCalls.add(idMapper.getCall(id));
         }
 
-        if (mIsIncoming) {
+        if (mIsUnknown) {
+            for (Listener l : mListeners) {
+                l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection.getState()));
+            }
+        } else if (mIsIncoming) {
             // We do not handle incoming calls immediately when they are verified by the connection
             // service. We allow the caller-info-query code to execute first so that we can read the
             // direct-to-voicemail property before deciding if we want to show the incoming call to
@@ -686,7 +701,11 @@
         setDisconnectCause(disconnectCause);
         CallsManager.getInstance().markCallAsDisconnected(this, disconnectCause);
 
-        if (mIsIncoming) {
+        if (mIsUnknown) {
+            for (Listener listener : mListeners) {
+                listener.onFailedUnknownCall(this);
+            }
+        } else if (mIsIncoming) {
             for (Listener listener : mListeners) {
                 listener.onFailedIncomingCall(this);
             }
@@ -1255,6 +1274,14 @@
         }
     }
 
+    public boolean isUnknown() {
+        return mIsUnknown;
+    }
+
+    public void setIsUnknown(boolean isUnknown) {
+        mIsUnknown = isUnknown;
+    }
+
     static int getStateFromConnectionState(int state) {
         switch (state) {
             case Connection.STATE_INITIALIZING:
diff --git a/src/com/android/server/telecom/CallReceiver.java b/src/com/android/server/telecom/CallReceiver.java
index c8fdeb4..17ca3e5 100644
--- a/src/com/android/server/telecom/CallReceiver.java
+++ b/src/com/android/server/telecom/CallReceiver.java
@@ -21,16 +21,21 @@
 public class CallReceiver extends BroadcastReceiver {
     private static final String TAG = CallReceiver.class.getName();
 
+    static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
     static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
     static final String KEY_IS_DEFAULT_DIALER =
             "is_default_dialer";
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
         final boolean isIncomingCall = intent.getBooleanExtra(KEY_IS_INCOMING_CALL, false);
-        Log.i(TAG, "onReceive - isIncomingCall: %s", isIncomingCall);
+        Log.i(this, "onReceive - isIncomingCall: %s isUnknownCall: %s", isIncomingCall,
+                isUnknownCall);
 
-        if (isIncomingCall) {
+        if (isUnknownCall) {
+            processUnknownCallIntent(intent);
+        } else if (isIncomingCall) {
             processIncomingCallIntent(intent);
         } else {
             processOutgoingCallIntent(context, intent);
@@ -111,6 +116,22 @@
         getCallsManager().processIncomingCallIntent(phoneAccountHandle, clientExtras);
     }
 
+    private void processUnknownCallIntent(Intent intent) {
+        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
+                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+
+        if (phoneAccountHandle == null) {
+            Log.w(this, "Rejecting unknown call due to null phone account");
+            return;
+        }
+        if (phoneAccountHandle.getComponentName() == null) {
+            Log.w(this, "Rejecting unknown call due to null component name");
+            return;
+        }
+
+        getCallsManager().addNewUnknownCall(phoneAccountHandle, intent.getExtras());
+    }
+
     static CallsManager getCallsManager() {
         return CallsManager.getInstance();
     }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 1b0a9e3..52c0b44 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -32,6 +32,7 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.util.IndentingPrintWriter;
+
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 
@@ -226,6 +227,20 @@
     }
 
     @Override
+    public void onSuccessfulUnknownCall(Call call, int callState) {
+        setCallState(call, callState);
+        Log.i(this, "onSuccessfulUnknownCall for call %s", call);
+        addCall(call);
+    }
+
+    @Override
+    public void onFailedUnknownCall(Call call) {
+        Log.i(this, "onFailedUnknownCall for call %s", call);
+        setCallState(call, CallState.DISCONNECTED);
+        call.removeListener(this);
+    }
+
+    @Override
     public void onRingbackRequested(Call call, boolean ringback) {
         for (CallsManagerListener listener : mListeners) {
             listener.onRingbackRequested(call, ringback);
@@ -336,6 +351,27 @@
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
+    void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+        Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE);
+        Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
+        Call call = new Call(
+                mContext,
+                mConnectionServiceRepository,
+                handle,
+                null /* gatewayInfo */,
+                null /* connectionManagerPhoneAccount */,
+                phoneAccountHandle,
+                // Use onCreateIncomingConnection in TelephonyConnectionService, so that we attach
+                // to the existing connection instead of trying to create a new one.
+                true /* isIncoming */,
+                false /* isConference */);
+        call.setConnectTimeMillis(System.currentTimeMillis());
+        call.setIsUnknown(true);
+        call.setExtras(extras);
+        call.addListener(this);
+        call.startCreateConnection(mPhoneAccountRegistrar);
+    }
+
     /**
      * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
      *
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index cacdc81..32c6107 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -643,7 +643,8 @@
                                     call.getHandle(),
                                     extras,
                                     call.getVideoState()),
-                            call.isIncoming());
+                            call.isIncoming(),
+                            call.isUnknown());
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
                     mPendingResponses.remove(callId).handleCreateConnectionFailure(
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 6aae49b..0c6e25d 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -31,7 +31,7 @@
 import java.util.Objects;
 
 /**
- * This class creates connections to place new outgoing calls to attached to an existing incoming
+ * This class creates connections to place new outgoing calls or to attach to an existing incoming
  * call. In either case, this class cycles through a set of connection services until:
  *   - a connection service returns a newly created connection in which case the call is displayed
  *     to the user
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 299ca24..fbbe1c9 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -37,6 +37,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 
+
 // TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.util.IndentingPrintWriter;
@@ -472,6 +473,29 @@
         }
     }
 
+    /**
+     * @see android.telecom.TelecomManager#addNewUnknownCall
+     */
+    @Override
+    public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+        if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null &&
+                TelephonyUtil.isPstnComponentName(phoneAccountHandle.getComponentName())) {
+            mAppOpsManager.checkPackage(
+                    Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
+
+            Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
+            intent.setClass(mContext, CallReceiver.class);
+            intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            intent.putExtras(extras);
+            intent.putExtra(CallReceiver.KEY_IS_UNKNOWN_CALL, true);
+            intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+            mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+        } else {
+            Log.i(this, "Null phoneAccountHandle or not initiated by Telephony. Ignoring request"
+                    + " to add new unknown call.");
+        }
+    }
+
     //
     // Supporting methods for the ITelecomService interface implementation.
     //
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index cd9b77f..9c09a40 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -69,6 +69,7 @@
             </intent-filter>
             <intent-filter>
                 <action android:name="android.telecom.testapps.ACTION_START_INCOMING_CALL" />
+                <action android:name="android.telecom.testapps.ACTION_NEW_UNKNOWN_CALL" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="tel" />
             </intent-filter>
diff --git a/tests/src/com/android/server/telecom/testapps/CallNotificationReceiver.java b/tests/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
index 6105f3e..a835bf1 100644
--- a/tests/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
+++ b/tests/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
@@ -22,14 +22,18 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.telecom.CallState;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.util.Log;
 
 /**
  * This class receives the notification callback intents used to update call states for
  * {@link TestConnectionService}.
  */
 public class CallNotificationReceiver extends BroadcastReceiver {
+
+    static final String TAG = CallNotificationReceiver.class.getSimpleName();
     /**
      * Exit intent action is sent when the user clicks the "exit" action of the
      * TestConnectionService notification. Used to cancel (remove) the notification.
@@ -83,4 +87,22 @@
 
         TelecomManager.from(context).addNewIncomingCall(phoneAccount, extras);
     }
+
+    public static void addNewUnknownCall(Context context, Uri handle, Bundle extras) {
+        Log.i(TAG, "Adding new unknown call with handle " + handle);
+        PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
+                new ComponentName(context, TestConnectionService.class),
+                CallServiceNotifier.SIM_SUBSCRIPTION_ID);
+
+        if (extras == null) {
+            extras = new Bundle();
+        }
+
+        if (handle != null) {
+            extras.putParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE, handle);
+            extras.putParcelable(TestConnectionService.EXTRA_HANDLE, handle);
+        }
+
+        TelecomManager.from(context).addNewUnknownCall(phoneAccount, extras);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/testapps/TestCallActivity.java b/tests/src/com/android/server/telecom/testapps/TestCallActivity.java
index f5e24ec..9c9a40b 100644
--- a/tests/src/com/android/server/telecom/testapps/TestCallActivity.java
+++ b/tests/src/com/android/server/telecom/testapps/TestCallActivity.java
@@ -36,21 +36,25 @@
     public static final String ACTION_NEW_INCOMING_CALL =
             "android.telecom.testapps.ACTION_START_INCOMING_CALL";
 
+    /*
+     * Action to exercise TelecomManager.addNewUnknownCall().
+     */
+    public static final String ACTION_NEW_UNKNOWN_CALL =
+            "android.telecom.testapps.ACTION_NEW_UNKNOWN_CALL";
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         final Intent intent = getIntent();
-        if (intent != null && intent.getData() != null) {
-            startIncomingCallBroadcast(intent.getData());
+        final String action = intent != null ? intent.getAction() : null;
+        final Uri data = intent != null ? intent.getData() : null;
+        if (ACTION_NEW_INCOMING_CALL.equals(action) && data != null) {
+            CallNotificationReceiver.sendIncomingCallIntent(this, data, false);
+        } if (ACTION_NEW_UNKNOWN_CALL.equals(action) && data != null) {
+            CallNotificationReceiver.addNewUnknownCall(this, data, intent.getExtras());
+        } else {
+            CallServiceNotifier.getInstance().updateNotification(this);
         }
-        CallServiceNotifier.getInstance().updateNotification(this);
         finish();
     }
-
-    /**
-     * Bypass the notification and start the test incoming call directly.
-     */
-    private void startIncomingCallBroadcast(Uri handle) {
-        CallNotificationReceiver.sendIncomingCallIntent(this, handle, false);
-    }
 }
diff --git a/tests/src/com/android/server/telecom/testapps/TestConnectionService.java b/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
index b327462..389373f 100644
--- a/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -267,6 +267,7 @@
                 originalHandle + "]");
 
         final TestConnection connection = new TestConnection(false /* isIncoming */);
+        connection.setAddress(handle, TelecomManager.PRESENTATION_ALLOWED);
 
         // If the number starts with 555, then we handle it ourselves. If not, then we
         // use a remote connection service.
@@ -341,6 +342,31 @@
         }
     }
 
+    @Override
+    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+            final ConnectionRequest request) {
+        PhoneAccountHandle accountHandle = request.getAccountHandle();
+        ComponentName componentName = new ComponentName(this, TestConnectionService.class);
+        if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
+            final TestConnection connection = new TestConnection(false);
+            final Bundle extras = request.getExtras();
+            final Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
+
+            Uri handle = providedHandle == null ?
+                    Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(false), null)
+                    : providedHandle;
+
+            connection.setAddress(handle,  TelecomManager.PRESENTATION_ALLOWED);
+            connection.setDialing();
+
+            addCall(connection);
+            return connection;
+        } else {
+            return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.ERROR,
+                    "Invalid inputs: " + accountHandle + " " + componentName));
+        }
+    }
+
     private void activateCall(TestConnection connection) {
         if (mMediaPlayer == null) {
             mMediaPlayer = createMediaPlayer();