Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6dc1c75f authored by Yorke Lee's avatar Yorke Lee
Browse files

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
parent 1082d72d
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -196,5 +196,10 @@
                android:theme="@style/Theme.Telecomm.Transparent">
        </activity>

        <receiver android:name=".CallReceiver"
            android:exported="false">
        </receiver>


    </application>
</manifest>
+11 −117
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ import android.widget.Toast;
// 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 @@ import android.widget.Toast;
 */
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 @@ public class CallActivity extends Activity {
    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 @@ public class CallActivity extends Activity {
        }
    }

    /**
     * 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 @@ public class CallActivity extends Activity {
            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;
        intent.putExtra(CallReceiver.KEY_IS_DEFAULT_DIALER, isDefaultDialer());
        sendBroadcastToReceiver(intent, false /* isIncoming */);
    }

        // 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);
        }
    }

    /**
     * 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 @@ public class CallActivity extends Activity {
                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);
    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;
    }
        return false;
    }

    CallsManager getCallsManager() {
        return CallsManager.getInstance();
    }
}
+133 −0
Original line number Diff line number Diff line
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);
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -279,7 +279,7 @@ class NewOutgoingCallIntentBroadcaster {

        mContext.sendOrderedBroadcastAsUser(
                broadcastIntent,
                UserHandle.OWNER,
                UserHandle.CURRENT,
                PERMISSION,
                receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
                null,  // scheduler
+3 −1
Original line number Diff line number Diff line
@@ -466,7 +466,9 @@ public class TelecomServiceImpl extends ITelecomService.Stub {
                intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
            }

            mContext.startActivity(intent);
            long token = Binder.clearCallingIdentity();
            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
            Binder.restoreCallingIdentity(token);
        }
    }