1package com.android.server.telecom;
2
3import android.content.BroadcastReceiver;
4import android.content.Context;
5import android.content.Intent;
6import android.net.Uri;
7import android.os.Bundle;
8import android.os.UserHandle;
9import android.telecom.PhoneAccount;
10import android.telecom.PhoneAccountHandle;
11import android.telecom.TelecomManager;
12import android.telephony.DisconnectCause;
13import android.telephony.PhoneNumberUtils;
14
15/**
16 * Single point of entry for all outgoing and incoming calls. {@link CallActivity} serves as a
17 * trampoline activity that captures call intents for individual users and forwards it to
18 * the {@link CallReceiver} which interacts with the rest of Telecom, both of which run only as
19 * the primary user.
20 */
21public class CallReceiver extends BroadcastReceiver {
22    private static final String TAG = CallReceiver.class.getName();
23
24    static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
25    static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
26    static final String KEY_IS_DEFAULT_DIALER =
27            "is_default_dialer";
28
29    @Override
30    public void onReceive(Context context, Intent intent) {
31        final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
32        final boolean isIncomingCall = intent.getBooleanExtra(KEY_IS_INCOMING_CALL, false);
33        Log.i(this, "onReceive - isIncomingCall: %s isUnknownCall: %s", isIncomingCall,
34                isUnknownCall);
35
36        if (isUnknownCall) {
37            processUnknownCallIntent(intent);
38        } else if (isIncomingCall) {
39            processIncomingCallIntent(intent);
40        } else {
41            processOutgoingCallIntent(context, intent);
42        }
43    }
44
45    /**
46     * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
47     *
48     * @param intent Call intent containing data about the handle to call.
49     */
50    static void processOutgoingCallIntent(Context context, Intent intent) {
51        Uri handle = intent.getData();
52        String scheme = handle.getScheme();
53        String uriString = handle.getSchemeSpecificPart();
54
55        if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
56            handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
57                    PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
58        }
59
60        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
61                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
62
63        Bundle clientExtras = null;
64        if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
65            clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
66        }
67        if (clientExtras == null) {
68            clientExtras = Bundle.EMPTY;
69        }
70
71        final boolean isDefaultDialer = intent.getBooleanExtra(KEY_IS_DEFAULT_DIALER, false);
72
73        // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
74        Call call = getCallsManager().startOutgoingCall(handle, phoneAccountHandle, clientExtras);
75
76        if (call != null) {
77            // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
78            // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
79            // killed if memory is scarce. However, this is OK here because the entire Telecom
80            // process will be running throughout the duration of the phone call and should never
81            // be killed.
82            NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
83                    context, getCallsManager(), call, intent, isDefaultDialer);
84            final int result = broadcaster.processIntent();
85            final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
86
87            if (!success && call != null) {
88                disconnectCallAndShowErrorDialog(context, call, result);
89            }
90        }
91    }
92
93    static void processIncomingCallIntent(Intent intent) {
94        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
95                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
96
97        if (phoneAccountHandle == null) {
98            Log.w(TAG, "Rejecting incoming call due to null phone account");
99            return;
100        }
101        if (phoneAccountHandle.getComponentName() == null) {
102            Log.w(TAG, "Rejecting incoming call due to null component name");
103            return;
104        }
105
106        Bundle clientExtras = null;
107        if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
108            clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
109        }
110        if (clientExtras == null) {
111            clientExtras = Bundle.EMPTY;
112        }
113
114        Log.d(TAG, "Processing incoming call from connection service [%s]",
115                phoneAccountHandle.getComponentName());
116        getCallsManager().processIncomingCallIntent(phoneAccountHandle, clientExtras);
117    }
118
119    private void processUnknownCallIntent(Intent intent) {
120        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
121                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
122
123        if (phoneAccountHandle == null) {
124            Log.w(this, "Rejecting unknown call due to null phone account");
125            return;
126        }
127        if (phoneAccountHandle.getComponentName() == null) {
128            Log.w(this, "Rejecting unknown call due to null component name");
129            return;
130        }
131
132        getCallsManager().addNewUnknownCall(phoneAccountHandle, intent.getExtras());
133    }
134
135    static CallsManager getCallsManager() {
136        return CallsManager.getInstance();
137    }
138
139    private static void disconnectCallAndShowErrorDialog(
140            Context context, Call call, int errorCode) {
141        call.disconnect();
142        final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
143        int errorMessageId = -1;
144        switch (errorCode) {
145            case DisconnectCause.INVALID_NUMBER:
146            case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
147                errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
148                break;
149        }
150        if (errorMessageId != -1) {
151            errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
152        }
153        errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
154        context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
155    }
156}
157