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