Split CallActivity into trampoline activity and CallReceiver
Move most of CallActivity logic into CallReceiver. Only significant logic
remaining in CallActivity is checking for OUTGOING_CALL_RESTRICTED and
showing a toast.
Also fixes a bug where the secondary user would never be able to make
a call if the primary user had Google Voice installed, because the
ordered broadcast is sent out for the primary user, allowing Google
voice to cancel it.
Bug: 17579886
Change-Id: Id1689f36503a74e5754b10d019a8b64024d27f77
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 706dda7..2c96df6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -196,5 +196,10 @@
android:theme="@style/Theme.Telecomm.Transparent">
</activity>
+ <receiver android:name=".CallReceiver"
+ android:exported="false">
+ </receiver>
+
+
</application>
</manifest>
diff --git a/src/com/android/server/telecom/CallActivity.java b/src/com/android/server/telecom/CallActivity.java
index 07b6f7e..680e387 100644
--- a/src/com/android/server/telecom/CallActivity.java
+++ b/src/com/android/server/telecom/CallActivity.java
@@ -35,7 +35,7 @@
// TODO: Needed for move to system service: import com.android.internal.R;
/**
- * Activity that handles system CALL actions and forwards them to {@link CallsManager}.
+ * Activity that handles system CALL actions and forwards them to {@link CallReceiver}.
* Handles all three CALL action types: CALL, CALL_PRIVILEGED, and CALL_EMERGENCY.
*
* Pre-L, the only way apps were were allowed to make outgoing emergency calls was the
@@ -53,12 +53,6 @@
*/
public class CallActivity extends Activity {
- /**
- * {@inheritDoc}
- *
- * This method is the single point of entry for the CALL intent, which is used by built-in apps
- * like Contacts & Dialer, as well as 3rd party apps to initiate outgoing calls.
- */
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
@@ -81,7 +75,6 @@
private void processIntent(Intent intent) {
// Ensure call intents are not processed on devices that are not capable of calling.
if (!isVoiceCapable()) {
- setResult(RESULT_CANCELED);
return;
}
@@ -96,11 +89,6 @@
}
}
- /**
- * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
- *
- * @param intent Call intent containing data about the handle to call.
- */
private void processOutgoingCallIntent(Intent intent) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
@@ -123,74 +111,12 @@
return;
}
- // This must come after the code which checks to see if this user is allowed to place
- // outgoing calls.
- if (maybeSwitchToPrimaryUser(intent, true /* isForResult */)) {
- return;
- }
-
- PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
- TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-
- Bundle clientExtras = null;
- if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
- clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
- }
- if (clientExtras == null) {
- clientExtras = Bundle.EMPTY;
- }
-
- // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
- Call call = getCallsManager().startOutgoingCall(handle, phoneAccountHandle, clientExtras);
-
- if (call == null) {
- setResult(RESULT_CANCELED);
- } else {
- NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
- this, getCallsManager(), call, intent, isDefaultDialer());
- final int result = broadcaster.processIntent();
- final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
-
- if (!success && call != null) {
- disconnectCallAndShowErrorDialog(call, result);
- }
- setResult(success ? RESULT_OK : RESULT_CANCELED);
- }
+ intent.putExtra(CallReceiver.KEY_IS_DEFAULT_DIALER, isDefaultDialer());
+ sendBroadcastToReceiver(intent, false /* isIncoming */);
}
- /**
- * Processes INCOMING_CALL intents. Grabs the connection service information from the intent
- * extra and forwards that to the CallsManager to start the incoming call flow.
- *
- * @param intent The incoming call intent.
- */
private void processIncomingCallIntent(Intent intent) {
- PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
- TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
- if (phoneAccountHandle == null) {
- Log.w(this, "Rejecting incoming call due to null phone account");
- return;
- }
- if (phoneAccountHandle.getComponentName() == null) {
- Log.w(this, "Rejecting incoming call due to null component name");
- return;
- }
-
- if (maybeSwitchToPrimaryUser(intent, false /* isForResult */)) {
- return;
- }
-
- Bundle clientExtras = null;
- if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
- clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
- }
- if (clientExtras == null) {
- clientExtras = Bundle.EMPTY;
- }
-
- Log.d(this, "Processing incoming call from connection service [%s]",
- phoneAccountHandle.getComponentName());
- getCallsManager().processIncomingCallIntent(phoneAccountHandle, clientExtras);
+ sendBroadcastToReceiver(intent, true /* isIncoming */);
}
private boolean isDefaultDialer() {
@@ -216,46 +142,14 @@
com.android.internal.R.bool.config_voice_capable);
}
- private void disconnectCallAndShowErrorDialog(Call call, int errorCode) {
- call.disconnect();
- final Intent errorIntent = new Intent(this, ErrorDialogActivity.class);
- int errorMessageId = -1;
- switch (errorCode) {
- case DisconnectCause.INVALID_NUMBER:
- errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
- break;
- case DisconnectCause.VOICEMAIL_NUMBER_MISSING:
- errorIntent.putExtra(ErrorDialogActivity.SHOW_MISSING_VOICEMAIL_NO_DIALOG_EXTRA,
- true);
- break;
- }
- if (errorMessageId != -1) {
- errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
- }
- startActivity(errorIntent);
- }
-
/**
- * Checks to see if we are running as a secondary user and if so, starts an activity with the
- * specified intent on the primary user.
- *
- * @return True if the intent was resent to the primary user, false otherwise.
+ * Trampolines the intent to the broadcast receiver that runs only as the primary user.
*/
- private boolean maybeSwitchToPrimaryUser(Intent intent, boolean isForResult) {
- // Check to see if the we are running as a secondary user and if so, forward the intent to
- // the primary user. The core of Telecom only runs in the primary user space so this in
- // necessary for secondary users.
- if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
- if (isForResult) {
- intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
- }
- startActivityAsUser(intent, UserHandle.OWNER);
- return true;
- }
- return false;
- }
-
- CallsManager getCallsManager() {
- return CallsManager.getInstance();
+ private boolean sendBroadcastToReceiver(Intent intent, boolean incoming) {
+ intent.putExtra(CallReceiver.KEY_IS_INCOMING_CALL, incoming);
+ intent.setClass(this, CallReceiver.class);
+ Log.d(this, "Sending broadcast as user to CallReceiver- isIncoming: %s", incoming);
+ sendBroadcastAsUser(intent, UserHandle.OWNER);
+ return true;
}
}
diff --git a/src/com/android/server/telecom/CallReceiver.java b/src/com/android/server/telecom/CallReceiver.java
new file mode 100644
index 0000000..fe82b67
--- /dev/null
+++ b/src/com/android/server/telecom/CallReceiver.java
@@ -0,0 +1,133 @@
+package com.android.server.telecom;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.DisconnectCause;
+import android.telephony.PhoneNumberUtils;
+
+/**
+ * Single point of entry for all outgoing and incoming calls. {@link CallActivity} serves as a
+ * trampoline activity that captures call intents for individual users and forwards it to
+ * the {@link CallReceiver} which interacts with the rest of Telecom, both of which run only as
+ * the primary user.
+ */
+public class CallReceiver extends BroadcastReceiver {
+
+ 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 isIncomingCall = intent.getBooleanExtra(KEY_IS_INCOMING_CALL, false);
+ Log.d(this, "onReceive - isIncomingCall: %s", isIncomingCall);
+
+ if (isIncomingCall) {
+ processIncomingCallIntent(intent);
+ } else {
+ processOutgoingCallIntent(context, intent);
+ }
+ }
+
+ /**
+ * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
+ *
+ * @param intent Call intent containing data about the handle to call.
+ */
+ private void processOutgoingCallIntent(Context context, Intent intent) {
+ Uri handle = intent.getData();
+ String scheme = handle.getScheme();
+ String uriString = handle.getSchemeSpecificPart();
+
+ if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
+ handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
+ PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
+ }
+
+ PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+
+ Bundle clientExtras = null;
+ if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
+ clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
+ }
+ if (clientExtras == null) {
+ clientExtras = Bundle.EMPTY;
+ }
+
+ final boolean isDefaultDialer = intent.getBooleanExtra(KEY_IS_DEFAULT_DIALER, false);
+
+ // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
+ Call call = getCallsManager().startOutgoingCall(handle, phoneAccountHandle, clientExtras);
+
+ if (call != null) {
+ // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
+ // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
+ // killed if memory is scarce. However, this is OK here because the entire Telecom
+ // process will be running throughout the duration of the phone call and should never
+ // be killed.
+ NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
+ context, getCallsManager(), call, intent, isDefaultDialer);
+ final int result = broadcaster.processIntent();
+ final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
+
+ if (!success && call != null) {
+ disconnectCallAndShowErrorDialog(context, call, result);
+ }
+ }
+ }
+
+ private void processIncomingCallIntent(Intent intent) {
+ PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+
+ if (phoneAccountHandle == null) {
+ Log.w(this, "Rejecting incoming call due to null phone account");
+ return;
+ }
+ if (phoneAccountHandle.getComponentName() == null) {
+ Log.w(this, "Rejecting incoming call due to null component name");
+ return;
+ }
+
+ Bundle clientExtras = null;
+ if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
+ clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
+ }
+ if (clientExtras == null) {
+ clientExtras = Bundle.EMPTY;
+ }
+
+ Log.d(this, "Processing incoming call from connection service [%s]",
+ phoneAccountHandle.getComponentName());
+ getCallsManager().processIncomingCallIntent(phoneAccountHandle, clientExtras);
+ }
+
+ CallsManager getCallsManager() {
+ return CallsManager.getInstance();
+ }
+
+ private void disconnectCallAndShowErrorDialog(Context context, Call call, int errorCode) {
+ call.disconnect();
+ final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
+ int errorMessageId = -1;
+ switch (errorCode) {
+ case DisconnectCause.INVALID_NUMBER:
+ case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
+ errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
+ break;
+ }
+ if (errorMessageId != -1) {
+ errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
+ }
+ errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
+ }
+}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index cd2195c..4e866a1 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -279,7 +279,7 @@
mContext.sendOrderedBroadcastAsUser(
broadcastIntent,
- UserHandle.OWNER,
+ UserHandle.CURRENT,
PERMISSION,
receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
null, // scheduler
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index f0ac0a1..299ca24 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -466,7 +466,9 @@
intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
}
- mContext.startActivity(intent);
+ long token = Binder.clearCallingIdentity();
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ Binder.restoreCallingIdentity(token);
}
}