Home | History | Annotate | Download | only in telecom
      1 package com.android.server.telecom;
      2 
      3 import com.android.server.telecom.components.ErrorDialogActivity;
      4 
      5 import android.content.Context;
      6 import android.content.Intent;
      7 import android.net.Uri;
      8 import android.os.Bundle;
      9 import android.os.Trace;
     10 import android.os.UserHandle;
     11 import android.telecom.PhoneAccount;
     12 import android.telecom.PhoneAccountHandle;
     13 import android.telecom.TelecomManager;
     14 import android.telecom.VideoProfile;
     15 import android.telephony.DisconnectCause;
     16 import android.telephony.PhoneNumberUtils;
     17 import android.widget.Toast;
     18 
     19 /**
     20  * Single point of entry for all outgoing and incoming calls.
     21  * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
     22  * captures call intents for individual users and forwards it to the {@link CallIntentProcessor}
     23  * which interacts with the rest of Telecom, both of which run only as the primary user.
     24  */
     25 public class CallIntentProcessor {
     26 
     27     public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
     28     public static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
     29     /*
     30      *  Whether or not the dialer initiating this outgoing call is the default dialer, or system
     31      *  dialer and thus allowed to make emergency calls.
     32      */
     33     public static final String KEY_IS_PRIVILEGED_DIALER = "is_privileged_dialer";
     34 
     35     private final Context mContext;
     36     private final CallsManager mCallsManager;
     37 
     38     public CallIntentProcessor(Context context, CallsManager callsManager) {
     39         this.mContext = context;
     40         this.mCallsManager = callsManager;
     41     }
     42 
     43     public void processIntent(Intent intent) {
     44         final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
     45         Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
     46 
     47         Trace.beginSection("processNewCallCallIntent");
     48         if (isUnknownCall) {
     49             processUnknownCallIntent(mCallsManager, intent);
     50         } else {
     51             processOutgoingCallIntent(mContext, mCallsManager, intent);
     52         }
     53         Trace.endSection();
     54     }
     55 
     56 
     57     /**
     58      * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
     59      *
     60      * @param intent Call intent containing data about the handle to call.
     61      */
     62     static void processOutgoingCallIntent(
     63             Context context,
     64             CallsManager callsManager,
     65             Intent intent) {
     66         if (shouldPreventDuplicateVideoCall(context, callsManager, intent)) {
     67             return;
     68         }
     69 
     70         Uri handle = intent.getData();
     71         String scheme = handle.getScheme();
     72         String uriString = handle.getSchemeSpecificPart();
     73 
     74         if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
     75             handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
     76                     PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
     77         }
     78 
     79         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
     80                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
     81 
     82         Bundle clientExtras = null;
     83         if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
     84             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
     85         }
     86         if (clientExtras == null) {
     87             clientExtras = new Bundle();
     88         }
     89 
     90         final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
     91 
     92         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
     93         Call call = callsManager.startOutgoingCall(handle, phoneAccountHandle, clientExtras);
     94 
     95         if (call != null) {
     96             // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
     97             // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
     98             // killed if memory is scarce. However, this is OK here because the entire Telecom
     99             // process will be running throughout the duration of the phone call and should never
    100             // be killed.
    101             NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
    102                     context, callsManager, call, intent, isPrivilegedDialer);
    103             final int result = broadcaster.processIntent();
    104             final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
    105 
    106             if (!success && call != null) {
    107                 disconnectCallAndShowErrorDialog(context, call, result);
    108             }
    109         }
    110     }
    111 
    112     static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
    113         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
    114                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
    115 
    116         if (phoneAccountHandle == null) {
    117             Log.w(CallIntentProcessor.class,
    118                     "Rejecting incoming call due to null phone account");
    119             return;
    120         }
    121         if (phoneAccountHandle.getComponentName() == null) {
    122             Log.w(CallIntentProcessor.class,
    123                     "Rejecting incoming call due to null component name");
    124             return;
    125         }
    126 
    127         Bundle clientExtras = null;
    128         if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
    129             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
    130         }
    131         if (clientExtras == null) {
    132             clientExtras = new Bundle();
    133         }
    134 
    135         Log.d(CallIntentProcessor.class,
    136                 "Processing incoming call from connection service [%s]",
    137                 phoneAccountHandle.getComponentName());
    138         callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
    139     }
    140 
    141     static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
    142         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
    143                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
    144 
    145         if (phoneAccountHandle == null) {
    146             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
    147             return;
    148         }
    149         if (phoneAccountHandle.getComponentName() == null) {
    150             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
    151             return;
    152         }
    153 
    154         callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
    155     }
    156 
    157     private static void disconnectCallAndShowErrorDialog(
    158             Context context, Call call, int errorCode) {
    159         call.disconnect();
    160         final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
    161         int errorMessageId = -1;
    162         switch (errorCode) {
    163             case DisconnectCause.INVALID_NUMBER:
    164             case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
    165                 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
    166                 break;
    167         }
    168         if (errorMessageId != -1) {
    169             errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
    170             errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    171             context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
    172         }
    173     }
    174 
    175     /**
    176      * Whether an outgoing video call should be prevented from going out. Namely, don't allow an
    177      * outgoing video call if there is already an ongoing video call. Notify the user if their call
    178      * is not sent.
    179      *
    180      * @return {@code true} if the outgoing call is a video call and should be prevented from going
    181      *     out, {@code false} otherwise.
    182      */
    183     private static boolean shouldPreventDuplicateVideoCall(
    184             Context context,
    185             CallsManager callsManager,
    186             Intent intent) {
    187         int intentVideoState = intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
    188                 VideoProfile.STATE_AUDIO_ONLY);
    189         if (VideoProfile.isAudioOnly(intentVideoState)
    190                 || !callsManager.hasVideoCall()) {
    191             return false;
    192         } else {
    193             // Display an error toast to the user.
    194             Toast.makeText(
    195                     context,
    196                     context.getResources().getString(R.string.duplicate_video_call_not_allowed),
    197                     Toast.LENGTH_LONG).show();
    198             return true;
    199         }
    200     }
    201 }
    202