• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.UserManager;
12 import android.telecom.DefaultDialerManager;
13 import android.telecom.Log;
14 import android.telecom.PhoneAccount;
15 import android.telecom.PhoneAccountHandle;
16 import android.telecom.TelecomManager;
17 import android.telecom.VideoProfile;
18 import android.telephony.DisconnectCause;
19 import android.telephony.PhoneNumberUtils;
20 import 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  */
28 public class CallIntentProcessor {
29     public interface Adapter {
processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent)30         void processOutgoingCallIntent(Context context, CallsManager callsManager,
31                 Intent intent);
processIncomingCallIntent(CallsManager callsManager, Intent intent)32         void processIncomingCallIntent(CallsManager callsManager, Intent intent);
processUnknownCallIntent(CallsManager callsManager, Intent intent)33         void processUnknownCallIntent(CallsManager callsManager, Intent intent);
34     }
35 
36     public static class AdapterImpl implements Adapter {
37         @Override
processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent)38         public void processOutgoingCallIntent(Context context, CallsManager callsManager,
39                 Intent intent) {
40             CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent);
41         }
42 
43         @Override
processIncomingCallIntent(CallsManager callsManager, Intent intent)44         public void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
45             CallIntentProcessor.processIncomingCallIntent(callsManager, intent);
46         }
47 
48         @Override
processUnknownCallIntent(CallsManager callsManager, Intent intent)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 
CallIntentProcessor(Context context, CallsManager callsManager)71     public CallIntentProcessor(Context context, CallsManager callsManager) {
72         this.mContext = context;
73         this.mCallsManager = callsManager;
74     }
75 
processIntent(Intent intent)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      */
processOutgoingCallIntent( Context context, CallsManager callsManager, Intent intent)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         // Ensure sip URIs dialed using TEL scheme get converted to SIP scheme.
105         if (PhoneAccount.SCHEME_TEL.equals(scheme) && PhoneNumberUtils.isUriNumber(uriString)) {
106             handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, 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         if (!callsManager.isSelfManaged(phoneAccountHandle,
131                 (UserHandle) intent.getParcelableExtra(KEY_INITIATING_USER))) {
132             boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
133             // Show the toast to warn user that it is a personal call though initiated in work
134             // profile.
135             if (fixedInitiatingUser) {
136                 Toast.makeText(context, R.string.toast_personal_call_msg, Toast.LENGTH_LONG).show();
137             }
138         } else {
139             Log.i(CallIntentProcessor.class,
140                     "processOutgoingCallIntent: skip initiating user check");
141         }
142 
143         UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
144 
145         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
146         Call call = callsManager
147                 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
148                         intent);
149 
150         if (call != null) {
151             sendNewOutgoingCallIntent(context, call, callsManager, intent);
152         }
153     }
154 
sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager, Intent intent)155     static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
156             Intent intent) {
157         // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
158         // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
159         // killed if memory is scarce. However, this is OK here because the entire Telecom
160         // process will be running throughout the duration of the phone call and should never
161         // be killed.
162         final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
163 
164         NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
165                 context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
166                 isPrivilegedDialer);
167         final int result = broadcaster.processIntent();
168         final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
169 
170         if (!success && call != null) {
171             disconnectCallAndShowErrorDialog(context, call, result);
172         }
173     }
174 
175     /**
176      * If the call is initiated from managed profile but there is no work dialer installed, treat
177      * the call is initiated from its parent user.
178      *
179      * @return whether the initiating user is fixed.
180      */
fixInitiatingUserIfNecessary(Context context, Intent intent)181     static boolean fixInitiatingUserIfNecessary(Context context, Intent intent) {
182         final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
183         if (UserUtil.isManagedProfile(context, initiatingUser)) {
184             boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context,
185                     initiatingUser.getIdentifier()).size() == 0;
186             if (noDialerInstalled) {
187                 final UserManager userManager = UserManager.get(context);
188                 UserHandle parentUserHandle =
189                         userManager.getProfileParent(
190                                 initiatingUser.getIdentifier()).getUserHandle();
191                 intent.putExtra(KEY_INITIATING_USER, parentUserHandle);
192 
193                 Log.i(CallIntentProcessor.class, "fixInitiatingUserIfNecessary: no dialer installed"
194                         + " for current user; setting initiator to parent %s" + parentUserHandle);
195                 return true;
196             }
197         }
198         return false;
199     }
200 
processIncomingCallIntent(CallsManager callsManager, Intent intent)201     static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
202         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
203                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
204 
205         if (phoneAccountHandle == null) {
206             Log.w(CallIntentProcessor.class,
207                     "Rejecting incoming call due to null phone account");
208             return;
209         }
210         if (phoneAccountHandle.getComponentName() == null) {
211             Log.w(CallIntentProcessor.class,
212                     "Rejecting incoming call due to null component name");
213             return;
214         }
215 
216         Bundle clientExtras = null;
217         if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
218             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
219         }
220         if (clientExtras == null) {
221             clientExtras = new Bundle();
222         }
223 
224         Log.d(CallIntentProcessor.class,
225                 "Processing incoming call from connection service [%s]",
226                 phoneAccountHandle.getComponentName());
227         callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
228     }
229 
processUnknownCallIntent(CallsManager callsManager, Intent intent)230     static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
231         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
232                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
233 
234         if (phoneAccountHandle == null) {
235             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
236             return;
237         }
238         if (phoneAccountHandle.getComponentName() == null) {
239             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
240             return;
241         }
242 
243         callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
244     }
245 
disconnectCallAndShowErrorDialog( Context context, Call call, int errorCode)246     private static void disconnectCallAndShowErrorDialog(
247             Context context, Call call, int errorCode) {
248         call.disconnect();
249         final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
250         int errorMessageId = -1;
251         switch (errorCode) {
252             case DisconnectCause.INVALID_NUMBER:
253             case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
254                 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
255                 break;
256         }
257         if (errorMessageId != -1) {
258             errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
259             errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
260             context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
261         }
262     }
263 }
264