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.DefaultDialerManager;
13import android.telecom.Log;
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        boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
131        // Show the toast to warn user that it is a personal call though initiated in work profile.
132        if (fixedInitiatingUser) {
133            Toast.makeText(context, R.string.toast_personal_call_msg, Toast.LENGTH_LONG).show();
134        }
135
136        UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
137
138        // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
139        Call call = callsManager
140                .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
141                        intent);
142
143        if (call != null) {
144            sendNewOutgoingCallIntent(context, call, callsManager, intent);
145        }
146    }
147
148    static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
149            Intent intent) {
150        // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
151        // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
152        // killed if memory is scarce. However, this is OK here because the entire Telecom
153        // process will be running throughout the duration of the phone call and should never
154        // be killed.
155        final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
156
157        NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
158                context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
159                isPrivilegedDialer);
160        final int result = broadcaster.processIntent();
161        final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
162
163        if (!success && call != null) {
164            disconnectCallAndShowErrorDialog(context, call, result);
165        }
166    }
167
168    /**
169     * If the call is initiated from managed profile but there is no work dialer installed, treat
170     * the call is initiated from its parent user.
171     *
172     * @return whether the initiating user is fixed.
173     */
174    static boolean fixInitiatingUserIfNecessary(Context context, Intent intent) {
175        final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
176        if (UserUtil.isManagedProfile(context, initiatingUser)) {
177            boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context,
178                    initiatingUser.getIdentifier()).size() == 0;
179            if (noDialerInstalled) {
180                final UserManager userManager = UserManager.get(context);
181                UserHandle parentUserHandle =
182                        userManager.getProfileParent(
183                                initiatingUser.getIdentifier()).getUserHandle();
184                intent.putExtra(KEY_INITIATING_USER, parentUserHandle);
185                return true;
186            }
187        }
188        return false;
189    }
190
191    static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
192        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
193                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
194
195        if (phoneAccountHandle == null) {
196            Log.w(CallIntentProcessor.class,
197                    "Rejecting incoming call due to null phone account");
198            return;
199        }
200        if (phoneAccountHandle.getComponentName() == null) {
201            Log.w(CallIntentProcessor.class,
202                    "Rejecting incoming call due to null component name");
203            return;
204        }
205
206        Bundle clientExtras = null;
207        if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
208            clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
209        }
210        if (clientExtras == null) {
211            clientExtras = new Bundle();
212        }
213
214        Log.d(CallIntentProcessor.class,
215                "Processing incoming call from connection service [%s]",
216                phoneAccountHandle.getComponentName());
217        callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
218    }
219
220    static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
221        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
222                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
223
224        if (phoneAccountHandle == null) {
225            Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
226            return;
227        }
228        if (phoneAccountHandle.getComponentName() == null) {
229            Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
230            return;
231        }
232
233        callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
234    }
235
236    private static void disconnectCallAndShowErrorDialog(
237            Context context, Call call, int errorCode) {
238        call.disconnect();
239        final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
240        int errorMessageId = -1;
241        switch (errorCode) {
242            case DisconnectCause.INVALID_NUMBER:
243            case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
244                errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
245                break;
246        }
247        if (errorMessageId != -1) {
248            errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
249            errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
250            context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
251        }
252    }
253}
254