1package com.android.server.telecom;
2
3import com.android.server.telecom.components.ErrorDialogActivity;
4
5import android.content.Context;
6import android.content.Intent;
7import android.net.Uri;
8import android.os.Bundle;
9import android.os.Trace;
10import android.os.UserHandle;
11import android.os.UserManager;
12import android.telecom.Connection;
13import android.telecom.DefaultDialerManager;
14import android.telecom.PhoneAccount;
15import android.telecom.PhoneAccountHandle;
16import android.telecom.TelecomManager;
17import android.telecom.VideoProfile;
18import android.telephony.DisconnectCause;
19import android.telephony.PhoneNumberUtils;
20import android.widget.Toast;
21
22/**
23 * Single point of entry for all outgoing and incoming calls.
24 * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
25 * captures call intents for individual users and forwards it to the {@link CallIntentProcessor}
26 * which interacts with the rest of Telecom, both of which run only as the primary user.
27 */
28public class CallIntentProcessor {
29    public interface Adapter {
30        void processOutgoingCallIntent(Context context, CallsManager callsManager,
31                Intent intent);
32        void processIncomingCallIntent(CallsManager callsManager, Intent intent);
33        void processUnknownCallIntent(CallsManager callsManager, Intent intent);
34    }
35
36    public static class AdapterImpl implements Adapter {
37        @Override
38        public void processOutgoingCallIntent(Context context, CallsManager callsManager,
39                Intent intent) {
40            CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent);
41        }
42
43        @Override
44        public void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
45            CallIntentProcessor.processIncomingCallIntent(callsManager, intent);
46        }
47
48        @Override
49        public void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
50            CallIntentProcessor.processUnknownCallIntent(callsManager, intent);
51        }
52    }
53
54    public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
55    public static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
56    /*
57     *  Whether or not the dialer initiating this outgoing call is the default dialer, or system
58     *  dialer and thus allowed to make emergency calls.
59     */
60    public static final String KEY_IS_PRIVILEGED_DIALER = "is_privileged_dialer";
61
62    /**
63     * The user initiating the outgoing call.
64     */
65    public static final String KEY_INITIATING_USER = "initiating_user";
66
67
68    private final Context mContext;
69    private final CallsManager mCallsManager;
70
71    public CallIntentProcessor(Context context, CallsManager callsManager) {
72        this.mContext = context;
73        this.mCallsManager = callsManager;
74    }
75
76    public void processIntent(Intent intent) {
77        final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
78        Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
79
80        Trace.beginSection("processNewCallCallIntent");
81        if (isUnknownCall) {
82            processUnknownCallIntent(mCallsManager, intent);
83        } else {
84            processOutgoingCallIntent(mContext, mCallsManager, intent);
85        }
86        Trace.endSection();
87    }
88
89
90    /**
91     * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
92     *
93     * @param intent Call intent containing data about the handle to call.
94     */
95    static void processOutgoingCallIntent(
96            Context context,
97            CallsManager callsManager,
98            Intent intent) {
99
100        Uri handle = intent.getData();
101        String scheme = handle.getScheme();
102        String uriString = handle.getSchemeSpecificPart();
103
104        if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
105            handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
106                    PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
107        }
108
109        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
110                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
111
112        Bundle clientExtras = null;
113        if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
114            clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
115        }
116        if (clientExtras == null) {
117            clientExtras = new Bundle();
118        }
119
120        // Ensure call subject is passed on to the connection service.
121        if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) {
122            String callsubject = intent.getStringExtra(TelecomManager.EXTRA_CALL_SUBJECT);
123            clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject);
124        }
125
126        final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
127                VideoProfile.STATE_AUDIO_ONLY);
128        clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
129
130        final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
131
132        boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
133        // Show the toast to warn user that it is a personal call though initiated in work profile.
134        if (fixedInitiatingUser) {
135            Toast.makeText(context, R.string.toast_personal_call_msg, Toast.LENGTH_LONG).show();
136        }
137
138        UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
139
140        // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
141        Call call = callsManager
142                .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser);
143
144        if (call != null) {
145            // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
146            // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
147            // killed if memory is scarce. However, this is OK here because the entire Telecom
148            // process will be running throughout the duration of the phone call and should never
149            // be killed.
150            NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
151                    context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
152                    isPrivilegedDialer);
153            final int result = broadcaster.processIntent();
154            final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
155
156            if (!success && call != null) {
157                disconnectCallAndShowErrorDialog(context, call, result);
158            }
159        }
160    }
161
162    /**
163     * If the call is initiated from managed profile but there is no work dialer installed, treat
164     * the call is initiated from its parent user.
165     *
166     * @return whether the initiating user is fixed.
167     */
168    static boolean fixInitiatingUserIfNecessary(Context context, Intent intent) {
169        final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
170        if (UserUtil.isManagedProfile(context, initiatingUser)) {
171            boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context,
172                    initiatingUser.getIdentifier()).size() == 0;
173            if (noDialerInstalled) {
174                final UserManager userManager = UserManager.get(context);
175                UserHandle parentUserHandle =
176                        userManager.getProfileParent(
177                                initiatingUser.getIdentifier()).getUserHandle();
178                intent.putExtra(KEY_INITIATING_USER, parentUserHandle);
179                return true;
180            }
181        }
182        return false;
183    }
184
185    static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
186        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
187                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
188
189        if (phoneAccountHandle == null) {
190            Log.w(CallIntentProcessor.class,
191                    "Rejecting incoming call due to null phone account");
192            return;
193        }
194        if (phoneAccountHandle.getComponentName() == null) {
195            Log.w(CallIntentProcessor.class,
196                    "Rejecting incoming call due to null component name");
197            return;
198        }
199
200        Bundle clientExtras = null;
201        if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
202            clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
203        }
204        if (clientExtras == null) {
205            clientExtras = new Bundle();
206        }
207
208        Log.d(CallIntentProcessor.class,
209                "Processing incoming call from connection service [%s]",
210                phoneAccountHandle.getComponentName());
211        callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
212    }
213
214    static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
215        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
216                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
217
218        if (phoneAccountHandle == null) {
219            Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
220            return;
221        }
222        if (phoneAccountHandle.getComponentName() == null) {
223            Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
224            return;
225        }
226
227        callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
228    }
229
230    private static void disconnectCallAndShowErrorDialog(
231            Context context, Call call, int errorCode) {
232        call.disconnect();
233        final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
234        int errorMessageId = -1;
235        switch (errorCode) {
236            case DisconnectCause.INVALID_NUMBER:
237            case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
238                errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
239                break;
240        }
241        if (errorMessageId != -1) {
242            errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
243            errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
244            context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
245        }
246    }
247}
248