1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.telecom;
18 
19 import android.Manifest;
20 import android.app.Activity;
21 import android.app.AppOpsManager;
22 import android.app.BroadcastOptions;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Trace;
29 import android.os.UserHandle;
30 import android.telecom.GatewayInfo;
31 import android.telecom.Log;
32 import android.telecom.PhoneAccount;
33 import android.telecom.PhoneAccountHandle;
34 import android.telecom.TelecomManager;
35 import android.telecom.VideoProfile;
36 import android.telephony.DisconnectCause;
37 import android.telephony.TelephonyManager;
38 import android.text.TextUtils;
39 
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.server.telecom.flags.FeatureFlags;
42 import com.android.server.telecom.callredirection.CallRedirectionProcessor;
43 
44 // TODO: Needed for move to system service: import com.android.internal.R;
45 
46 /**
47  * OutgoingCallIntentBroadcaster receives CALL and CALL_PRIVILEGED Intents, and broadcasts the
48  * ACTION_NEW_OUTGOING_CALL intent. ACTION_NEW_OUTGOING_CALL is an ordered broadcast intent which
49  * contains the phone number being dialed. Applications can use this intent to (1) see which numbers
50  * are being dialed, (2) redirect a call (change the number being dialed), or (3) prevent a call
51  * from being placed.
52  *
53  * After the other applications have had a chance to see the ACTION_NEW_OUTGOING_CALL intent, it
54  * finally reaches the {@link NewOutgoingCallBroadcastIntentReceiver}.
55  *
56  * Calls where no number is present (like for a CDMA "empty flash" or a nonexistent voicemail
57  * number) are exempt from being broadcast.
58  *
59  * Calls to emergency numbers are still broadcast for informative purposes. The call is placed
60  * prior to sending ACTION_NEW_OUTGOING_CALL and cannot be redirected nor prevented.
61  */
62 @VisibleForTesting
63 public class NewOutgoingCallIntentBroadcaster {
64     /**
65      * Legacy string constants used to retrieve gateway provider extras from intents. These still
66      * need to be copied from the source call intent to the destination intent in order to
67      * support third party gateway providers that are still using old string constants in
68      * Telephony.
69      */
70     public static final String EXTRA_GATEWAY_PROVIDER_PACKAGE =
71             "com.android.phone.extra.GATEWAY_PROVIDER_PACKAGE";
72     public static final String EXTRA_GATEWAY_URI = "com.android.phone.extra.GATEWAY_URI";
73 
74     private final CallsManager mCallsManager;
75     private Call mCall;
76     private final Intent mIntent;
77     private final Context mContext;
78     private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
79     private final TelecomSystem.SyncRoot mLock;
80     private final DefaultDialerCache mDefaultDialerCache;
81     private final MmiUtils mMmiUtils;
82     private final FeatureFlags mFeatureFlags;
83 
84     /*
85      * Whether or not the outgoing call intent originated from the default phone application. If
86      * so, it will be allowed to make emergency calls, even with the ACTION_CALL intent.
87      */
88     private final boolean mIsDefaultOrSystemPhoneApp;
89 
90     public static class CallDisposition {
91         // True for certain types of numbers that are not intended to be intercepted or modified
92         // by third parties (e.g. emergency numbers).
93         public boolean callImmediately = false;
94         // True for all managed calls, false for self-managed calls.
95         public boolean sendBroadcast = true;
96         // True for requesting call redirection, false for not requesting it.
97         public boolean requestRedirection = true;
98         public int disconnectCause = DisconnectCause.NOT_DISCONNECTED;
99         String number;
100         Uri callingAddress;
101     }
102 
103     @VisibleForTesting
NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter, boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils, FeatureFlags featureFlags)104     public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager,
105             Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
106             boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils,
107             FeatureFlags featureFlags) {
108         mContext = context;
109         mCallsManager = callsManager;
110         mIntent = intent;
111         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
112         mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
113         mLock = mCallsManager.getLock();
114         mDefaultDialerCache = defaultDialerCache;
115         mMmiUtils = mmiUtils;
116         mFeatureFlags = featureFlags;
117     }
118 
119     /**
120      * Processes the result of the outgoing call broadcast intent, and performs callbacks to
121      * the OutgoingCallIntentBroadcasterListener as necessary.
122      */
123     public class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver {
124 
125         @Override
onReceive(Context context, Intent intent)126         public void onReceive(Context context, Intent intent) {
127             try {
128                 Log.startSession("NOCBIR.oR");
129                 Trace.beginSection("onReceiveNewOutgoingCallBroadcast");
130                 synchronized (mLock) {
131                     Log.v(this, "onReceive: %s", intent);
132 
133                     // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is
134                     // used as the actual number to call. (If null, no call will be placed.)
135                     String resultNumber = getResultData();
136                     Log.i(NewOutgoingCallIntentBroadcaster.this,
137                             "Received new-outgoing-call-broadcast for %s with data %s", mCall,
138                             Log.pii(resultNumber));
139 
140                     boolean endEarly = false;
141                     long disconnectTimeout =
142                             Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver());
143                     if (resultNumber == null) {
144                         Log.v(this, "Call cancelled (null number), returning...");
145                         disconnectTimeout = getDisconnectTimeoutFromApp(
146                                 getResultExtras(false), disconnectTimeout);
147                         endEarly = true;
148                     } else if (isEmergencyNumber(resultNumber)) {
149                         Log.w(this, "Cannot modify outgoing call to emergency number %s.",
150                                 resultNumber);
151                         disconnectTimeout = 0;
152                         endEarly = true;
153                     }
154 
155                     if (endEarly) {
156                         if (mCall != null) {
157                             mCall.disconnect(disconnectTimeout);
158                         }
159                         return;
160                     }
161 
162                     // If this call is already disconnected then we have nothing more to do.
163                     if (mCall.isDisconnected()) {
164                         Log.w(this, "Call has already been disconnected," +
165                                         " ignore the broadcast Call %s", mCall);
166                         return;
167                     }
168 
169                     // TODO: Remove the assumption that phone numbers are either SIP or TEL.
170                     // This does not impact self-managed ConnectionServices as they do not use the
171                     // NewOutgoingCallIntentBroadcaster.
172                     Uri resultHandleUri = Uri.fromParts(
173                             mPhoneNumberUtilsAdapter.isUriNumber(resultNumber) ?
174                                     PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
175                             resultNumber, null);
176 
177                     Uri originalUri = mIntent.getData();
178 
179                     if (originalUri.getSchemeSpecificPart().equals(resultNumber)) {
180                         Log.v(this, "Call number unmodified after" +
181                                 " new outgoing call intent broadcast.");
182                     } else {
183                         Log.v(this, "Retrieved modified handle after outgoing call intent" +
184                                 " broadcast: Original: %s, Modified: %s",
185                                 Log.pii(originalUri),
186                                 Log.pii(resultHandleUri));
187                     }
188 
189                     GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
190                     placeOutgoingCallImmediately(mCall, resultHandleUri, gatewayInfo,
191                             mIntent.getBooleanExtra(
192                                     TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false),
193                             mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
194                                     VideoProfile.STATE_AUDIO_ONLY));
195                 }
196             } finally {
197                 Trace.endSection();
198                 Log.endSession();
199             }
200         }
201     }
202 
203     /**
204      * Processes the supplied intent and starts the outgoing call broadcast process relevant to the
205      * intent.
206      *
207      * This method will handle three kinds of actions:
208      *
209      * - CALL (intent launched by all third party dialers)
210      * - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer)
211      * - CALL_EMERGENCY (intent launched by lock screen emergency dialer)
212      *
213      * @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate
214      *         {@link DisconnectCause} if the call did not, describing why it failed.
215      */
216     @VisibleForTesting
evaluateCall()217     public CallDisposition evaluateCall() {
218         CallDisposition result = new CallDisposition();
219 
220         Intent intent = mIntent;
221         String action = intent.getAction();
222         final Uri handle = intent.getData();
223 
224         if (handle == null) {
225             Log.w(this, "Empty handle obtained from the call intent.");
226             result.disconnectCause = DisconnectCause.INVALID_NUMBER;
227             return result;
228         }
229 
230         boolean isVoicemailNumber = PhoneAccount.SCHEME_VOICEMAIL.equals(handle.getScheme());
231         if (isVoicemailNumber) {
232             if (Intent.ACTION_CALL.equals(action)
233                     || Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
234                 // Voicemail calls will be handled directly by the telephony connection manager
235                 Log.i(this, "Voicemail number dialed. Skipping redirection and broadcast", intent);
236                 mIntent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
237                         VideoProfile.STATE_AUDIO_ONLY);
238                 result.callImmediately = true;
239                 result.requestRedirection = false;
240                 result.sendBroadcast = false;
241                 result.callingAddress = handle;
242                 return result;
243             } else {
244                 Log.i(this, "Unhandled intent %s. Ignoring and not placing call.", intent);
245                 result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
246                 return result;
247             }
248         }
249 
250         PhoneAccountHandle targetPhoneAccount = mIntent.getParcelableExtra(
251                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
252         boolean isSelfManaged = false;
253         if (targetPhoneAccount != null) {
254             PhoneAccount phoneAccount =
255                     mCallsManager.getPhoneAccountRegistrar().getPhoneAccountUnchecked(
256                             targetPhoneAccount);
257             if (phoneAccount != null) {
258                 isSelfManaged = phoneAccount.isSelfManaged();
259             }
260         }
261 
262         result.number = "";
263         result.callingAddress = handle;
264 
265         if (isSelfManaged) {
266             // Self-managed call.
267             result.callImmediately = true;
268             result.sendBroadcast = false;
269             result.requestRedirection = false;
270             Log.i(this, "Skipping NewOutgoingCallBroadcast for self-managed call.");
271             return result;
272         }
273 
274         // Placing a managed call
275         String number = getNumberFromCallIntent(intent);
276         result.number = number;
277         if (number == null) {
278             result.disconnectCause = DisconnectCause.NO_PHONE_NUMBER_SUPPLIED;
279             return result;
280         }
281 
282         final boolean isEmergencyNumber = isEmergencyNumber(number);
283         Log.v(this, "isEmergencyNumber = %s", isEmergencyNumber);
284 
285         action = calculateCallIntentAction(intent, isEmergencyNumber);
286         intent.setAction(action);
287 
288         if (Intent.ACTION_CALL.equals(action)) {
289             if (isEmergencyNumber) {
290                 if (!mIsDefaultOrSystemPhoneApp) {
291                     Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s "
292                             + "unless caller is system or default dialer.", number, intent);
293                     launchSystemDialer(intent.getData());
294                     result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
295                     return result;
296                 } else {
297                     result.callImmediately = true;
298                     result.requestRedirection = false;
299                 }
300             } else if (mMmiUtils.isDangerousMmiOrVerticalCode(intent.getData())) {
301                 if (!mIsDefaultOrSystemPhoneApp) {
302                     Log.w(this,
303                             "Potentially dangerous MMI code %s with CALL Intent %s can only be "
304                                     + "sent if caller is the system or default dialer",
305                             number, intent);
306                     launchSystemDialer(intent.getData());
307                     result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
308                     return result;
309                 }
310             }
311         } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
312             if (!isEmergencyNumber) {
313                 Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL "
314                         + "Intent %s.", number, intent);
315                 result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
316                 return result;
317             }
318             result.callImmediately = true;
319             result.requestRedirection = false;
320         } else {
321             Log.w(this, "Unhandled Intent %s. Ignoring and not placing call.", intent);
322             result.disconnectCause = DisconnectCause.INVALID_NUMBER;
323             return result;
324         }
325 
326         String scheme = mPhoneNumberUtilsAdapter.isUriNumber(number)
327                 ? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL;
328         result.callingAddress = Uri.fromParts(scheme, number, null);
329 
330         return result;
331     }
332 
getNumberFromCallIntent(Intent intent)333     private String getNumberFromCallIntent(Intent intent) {
334         String number = null;
335 
336         Uri uri = intent.getData();
337         if (uri != null) {
338             String scheme = uri.getScheme();
339             if (scheme != null) {
340                 if (scheme.equals("tel") || scheme.equals("sip")) {
341                     number = uri.getSchemeSpecificPart();
342                 }
343             }
344         }
345 
346         if (TextUtils.isEmpty(number)) {
347             Log.w(this, "Empty number obtained from the call intent.");
348             return null;
349         }
350 
351         boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number);
352         if (!isUriNumber) {
353             number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number);
354             number = mPhoneNumberUtilsAdapter.stripSeparators(number);
355         }
356         return number;
357     }
358 
processCall(Call call, CallDisposition disposition)359     public void processCall(Call call, CallDisposition disposition) {
360         mCall = call;
361 
362         // If the new outgoing call broadast doesn't block, trigger the legacy process call
363         // behavior and exit out here.
364         if (!mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()) {
365             legacyProcessCall(disposition);
366             return;
367         }
368         boolean callRedirectionWithService = false;
369         // Only try to do redirection if it was requested and we're not calling immediately.
370         // We can expect callImmediately to be true for emergency calls and voip calls.
371         if (disposition.requestRedirection && !disposition.callImmediately) {
372             CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor(
373                     mContext, mCallsManager, mCall, disposition.callingAddress,
374                     mCallsManager.getPhoneAccountRegistrar(),
375                     getGateWayInfoFromIntent(mIntent, mIntent.getData()),
376                     mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
377                             false),
378                     mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
379                             VideoProfile.STATE_AUDIO_ONLY));
380             /**
381              * If there is an available {@link android.telecom.CallRedirectionService}, use the
382              * {@link CallRedirectionProcessor} to perform call redirection instead of using
383              * broadcasting.
384              */
385             callRedirectionWithService = callRedirectionProcessor
386                     .canMakeCallRedirectionWithServiceAsUser(mCall.getAssociatedUser());
387             if (callRedirectionWithService) {
388                 callRedirectionProcessor.performCallRedirection(mCall.getAssociatedUser());
389             }
390         }
391 
392         // If no redirection was kicked off, place the call now.
393         if (!callRedirectionWithService) {
394             callImmediately(disposition);
395         }
396 
397         // Finally, send the non-blocking broadcast if we're supposed to (ie for any non-voip call).
398         if (disposition.sendBroadcast) {
399             UserHandle targetUser = mCall.getAssociatedUser();
400             broadcastIntent(mIntent, disposition.number, false /* receiverRequired */, targetUser);
401         }
402     }
403 
404     /**
405      * The legacy non-flagged version of processing a call.  Although there is some code duplication
406      * if makes the new flow cleaner to read.
407      * @param disposition
408      */
legacyProcessCall(CallDisposition disposition)409     private void legacyProcessCall(CallDisposition disposition) {
410         if (disposition.callImmediately) {
411             callImmediately(disposition);
412 
413             // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
414             // so that third parties can still inspect (but not intercept) the outgoing call. When
415             // the broadcast finally reaches the OutgoingCallBroadcastReceiver, we'll know not to
416             // initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
417         }
418 
419         boolean callRedirectionWithService = false;
420         if (disposition.requestRedirection) {
421             CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor(
422                     mContext, mCallsManager, mCall, disposition.callingAddress,
423                     mCallsManager.getPhoneAccountRegistrar(),
424                     getGateWayInfoFromIntent(mIntent, mIntent.getData()),
425                     mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
426                             false),
427                     mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
428                             VideoProfile.STATE_AUDIO_ONLY));
429             /**
430              * If there is an available {@link android.telecom.CallRedirectionService}, use the
431              * {@link CallRedirectionProcessor} to perform call redirection instead of using
432              * broadcasting.
433              */
434             callRedirectionWithService = callRedirectionProcessor
435                     .canMakeCallRedirectionWithServiceAsUser(mCall.getAssociatedUser());
436             if (callRedirectionWithService) {
437                 callRedirectionProcessor.performCallRedirection(mCall.getAssociatedUser());
438             }
439         }
440 
441         if (disposition.sendBroadcast) {
442             UserHandle targetUser = mCall.getAssociatedUser();
443             broadcastIntent(mIntent, disposition.number,
444                     !disposition.callImmediately && !callRedirectionWithService, targetUser);
445         }
446     }
447 
448     /**
449      * Place a call immediately.
450      * @param disposition The disposition; used for retrieving the address of the call.
451      */
callImmediately(CallDisposition disposition)452     private void callImmediately(CallDisposition disposition) {
453         boolean speakerphoneOn = mIntent.getBooleanExtra(
454                 TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
455         int videoState = mIntent.getIntExtra(
456                 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
457                 VideoProfile.STATE_AUDIO_ONLY);
458         placeOutgoingCallImmediately(mCall, disposition.callingAddress, null,
459                 speakerphoneOn, videoState);
460     }
461 
462     /**
463      * Sends a new outgoing call ordered broadcast so that third party apps can cancel the
464      * placement of the call or redirect it to a different number.
465      *
466      * @param originalCallIntent The original call intent.
467      * @param number Call number that was stored in the original call intent.
468      * @param receiverRequired Whether or not the result from the ordered broadcast should be
469      *                         processed using a {@link NewOutgoingCallIntentBroadcaster}.
470      * @param targetUser User that the broadcast sent to.
471      */
broadcastIntent( Intent originalCallIntent, String number, boolean receiverRequired, UserHandle targetUser)472     private void broadcastIntent(
473             Intent originalCallIntent,
474             String number,
475             boolean receiverRequired,
476             UserHandle targetUser) {
477         Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
478         if (number != null) {
479             broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
480         }
481         Log.v(this, "Broadcasting intent: %s.", broadcastIntent);
482 
483         checkAndCopyProviderExtras(originalCallIntent, broadcastIntent);
484 
485         if (mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()) {
486             // Where the new outgoing call broadcast is unblocking, do not give receiver FG priority
487             // and do not allow background activity starts.
488             broadcastIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
489             Log.i(this, "broadcastIntent: Sending non-blocking for %s to %s", mCall.getId(),
490                     targetUser);
491             if (mFeatureFlags.telecomResolveHiddenDependencies()) {
492                 mContext.sendBroadcastAsUser(
493                         broadcastIntent,
494                         targetUser,
495                         Manifest.permission.PROCESS_OUTGOING_CALLS);
496             } else {
497                 mContext.sendBroadcastAsUser(
498                         broadcastIntent,
499                         targetUser,
500                         android.Manifest.permission.PROCESS_OUTGOING_CALLS,
501                         AppOpsManager.OP_PROCESS_OUTGOING_CALLS);  // initialExtras
502             }
503         } else {
504             Log.i(this, "broadcastIntent: Sending ordered for %s to %s, waitForResult=%b",
505                     mCall.getId(), targetUser, receiverRequired);
506             final BroadcastOptions options = BroadcastOptions.makeBasic();
507             options.setBackgroundActivityStartsAllowed(true);
508             // Force receivers of this broadcast intent to run at foreground priority because we
509             // want to finish processing the broadcast intent as soon as possible.
510             broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
511                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
512 
513             mContext.sendOrderedBroadcastAsUser(
514                     broadcastIntent,
515                     targetUser,
516                     android.Manifest.permission.PROCESS_OUTGOING_CALLS,
517                     AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
518                     options.toBundle(),
519                     receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
520                     null,  // scheduler
521                     Activity.RESULT_OK,  // initialCode
522                     number,  // initialData: initial value for the result data (number to be
523                              // modified)
524                     null);  // initialExtras
525         }
526     }
527 
528     /**
529      * Copy all the expected extras set when a 3rd party gateway provider is to be used, from the
530      * source intent to the destination one.
531      *
532      * @param src Intent which may contain the provider's extras.
533      * @param dst Intent where a copy of the extras will be added if applicable.
534      */
checkAndCopyProviderExtras(Intent src, Intent dst)535     public void checkAndCopyProviderExtras(Intent src, Intent dst) {
536         if (src == null) {
537             return;
538         }
539         if (hasGatewayProviderExtras(src)) {
540             dst.putExtra(EXTRA_GATEWAY_PROVIDER_PACKAGE,
541                     src.getStringExtra(EXTRA_GATEWAY_PROVIDER_PACKAGE));
542             dst.putExtra(EXTRA_GATEWAY_URI,
543                     src.getStringExtra(EXTRA_GATEWAY_URI));
544             Log.d(this, "Found and copied gateway provider extras to broadcast intent.");
545             return;
546         }
547 
548         Log.d(this, "No provider extras found in call intent.");
549     }
550 
551     /**
552      * Check if valid gateway provider information is stored as extras in the intent
553      *
554      * @param intent to check for
555      * @return true if the intent has all the gateway information extras needed.
556      */
hasGatewayProviderExtras(Intent intent)557     private boolean hasGatewayProviderExtras(Intent intent) {
558         final String name = intent.getStringExtra(EXTRA_GATEWAY_PROVIDER_PACKAGE);
559         final String uriString = intent.getStringExtra(EXTRA_GATEWAY_URI);
560 
561         return !TextUtils.isEmpty(name) && !TextUtils.isEmpty(uriString);
562     }
563 
getGatewayUriFromString(String gatewayUriString)564     private static Uri getGatewayUriFromString(String gatewayUriString) {
565         return TextUtils.isEmpty(gatewayUriString) ? null : Uri.parse(gatewayUriString);
566     }
567 
568     /**
569      * Extracts gateway provider information from a provided intent..
570      *
571      * @param intent to extract gateway provider information from.
572      * @param trueHandle The actual call handle that the user is trying to dial
573      * @return GatewayInfo object containing extracted gateway provider information as well as
574      *     the actual handle the user is trying to dial.
575      */
getGateWayInfoFromIntent(Intent intent, Uri trueHandle)576     public static GatewayInfo getGateWayInfoFromIntent(Intent intent, Uri trueHandle) {
577         if (intent == null) {
578             return null;
579         }
580 
581         // Check if gateway extras are present.
582         String gatewayPackageName = intent.getStringExtra(EXTRA_GATEWAY_PROVIDER_PACKAGE);
583         Uri gatewayUri = getGatewayUriFromString(intent.getStringExtra(EXTRA_GATEWAY_URI));
584         if (!TextUtils.isEmpty(gatewayPackageName) && gatewayUri != null) {
585             return new GatewayInfo(gatewayPackageName, gatewayUri, trueHandle);
586         }
587 
588         return null;
589     }
590 
placeOutgoingCallImmediately(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn, int videoState)591     private void placeOutgoingCallImmediately(Call call, Uri handle, GatewayInfo gatewayInfo,
592             boolean speakerphoneOn, int videoState) {
593         Log.i(this,
594                 "Placing call immediately instead of waiting for OutgoingCallBroadcastReceiver");
595         // Since we are not going to go through "Outgoing call broadcast", make sure
596         // we mark it as ready.
597         mCall.setNewOutgoingCallIntentBroadcastIsDone();
598         mCallsManager.placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState);
599     }
600 
launchSystemDialer(Uri handle)601     private void launchSystemDialer(Uri handle) {
602         Intent systemDialerIntent = new Intent();
603         systemDialerIntent.setComponent(mDefaultDialerCache.getDialtactsSystemDialerComponent());
604         systemDialerIntent.setAction(Intent.ACTION_DIAL);
605         systemDialerIntent.setData(handle);
606         systemDialerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
607         Log.v(this, "calling startActivity for default dialer: %s", systemDialerIntent);
608         mContext.startActivityAsUser(systemDialerIntent, UserHandle.CURRENT);
609     }
610 
611     /**
612      * Check whether or not this is an emergency number, in order to enforce the restriction
613      * that only the CALL_PRIVILEGED and CALL_EMERGENCY intents are allowed to make emergency
614      * calls.
615      *
616      * @param number number to inspect in order to determine whether or not an emergency number
617      * is being dialed
618      * @return True if the handle is an emergency number.
619      */
isEmergencyNumber(String number)620     private boolean isEmergencyNumber(String number) {
621         Log.v(this, "Checking restrictions for number : %s", Log.pii(number));
622         if (number == null) return false;
623         try {
624             return mContext.getSystemService(TelephonyManager.class).isEmergencyNumber(
625                     number);
626         } catch (UnsupportedOperationException uoe) {
627             Log.w(this, "isEmergencyNumber: Telephony not supported");
628             return false;
629         } catch (Exception e) {
630             Log.e(this, e, "isEmergencyNumber: Telephony threw an exception.");
631             return false;
632         }
633     }
634 
635     /**
636      * Given a call intent and whether or not the number to dial is an emergency number, determine
637      * the appropriate call intent action.
638      *
639      * @param intent Intent to evaluate
640      * @param isEmergencyNumber Whether or not the number is an emergency
641      * number.
642      * @return The appropriate action.
643      */
calculateCallIntentAction(Intent intent, boolean isEmergencyNumber)644     private String calculateCallIntentAction(Intent intent, boolean isEmergencyNumber) {
645         String action = intent.getAction();
646 
647         /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
648         if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
649             if (isEmergencyNumber) {
650                 Log.i(this, "ACTION_CALL_PRIVILEGED is used while the number is a"
651                         + " emergency number. Using ACTION_CALL_EMERGENCY as an action instead.");
652                 action = Intent.ACTION_CALL_EMERGENCY;
653             } else {
654                 action = Intent.ACTION_CALL;
655             }
656             Log.v(this, " - updating action from CALL_PRIVILEGED to %s", action);
657         }
658         return action;
659     }
660 
getDisconnectTimeoutFromApp(Bundle resultExtras, long defaultTimeout)661     private long getDisconnectTimeoutFromApp(Bundle resultExtras, long defaultTimeout) {
662         if (resultExtras != null) {
663             long disconnectTimeout = resultExtras.getLong(
664                     TelecomManager.EXTRA_NEW_OUTGOING_CALL_CANCEL_TIMEOUT, defaultTimeout);
665             if (disconnectTimeout < 0) {
666                 disconnectTimeout = 0;
667             }
668             return Math.min(disconnectTimeout,
669                     Timeouts.getMaxNewOutgoingCallCancelMillis(mContext.getContentResolver()));
670         } else {
671             return defaultTimeout;
672         }
673     }
674 }
675