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.Looper;
10 import android.os.Trace;
11 import android.os.UserHandle;
12 import android.os.UserManager;
13 import android.telecom.DefaultDialerManager;
14 import android.telecom.Log;
15 import android.telecom.Logging.Session;
16 import android.telecom.PhoneAccount;
17 import android.telecom.PhoneAccountHandle;
18 import android.telecom.TelecomManager;
19 import android.telecom.VideoProfile;
20 import android.telephony.DisconnectCause;
21 import android.telephony.PhoneNumberUtils;
22 import android.widget.Toast;
23 
24 import java.util.concurrent.CompletableFuture;
25 
26 /**
27  * Single point of entry for all outgoing and incoming calls.
28  * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
29  * captures call intents for individual users and forwards it to the {@link CallIntentProcessor}
30  * which interacts with the rest of Telecom, both of which run only as the primary user.
31  */
32 public class CallIntentProcessor {
33     public interface Adapter {
processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent, String callingPackage)34         void processOutgoingCallIntent(Context context, CallsManager callsManager,
35                 Intent intent, String callingPackage);
processIncomingCallIntent(CallsManager callsManager, Intent intent)36         void processIncomingCallIntent(CallsManager callsManager, Intent intent);
processUnknownCallIntent(CallsManager callsManager, Intent intent)37         void processUnknownCallIntent(CallsManager callsManager, Intent intent);
38     }
39 
40     public static class AdapterImpl implements Adapter {
41         private final DefaultDialerCache mDefaultDialerCache;
AdapterImpl(DefaultDialerCache cache)42         public AdapterImpl(DefaultDialerCache cache) {
43             mDefaultDialerCache = cache;
44         }
45 
46         @Override
processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent, String callingPackage)47         public void processOutgoingCallIntent(Context context, CallsManager callsManager,
48                 Intent intent, String callingPackage) {
49             CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent,
50                     callingPackage, mDefaultDialerCache);
51         }
52 
53         @Override
processIncomingCallIntent(CallsManager callsManager, Intent intent)54         public void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
55             CallIntentProcessor.processIncomingCallIntent(callsManager, intent);
56         }
57 
58         @Override
processUnknownCallIntent(CallsManager callsManager, Intent intent)59         public void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
60             CallIntentProcessor.processUnknownCallIntent(callsManager, intent);
61         }
62     }
63 
64     public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
65     public static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
66 
67     /**
68      * The user initiating the outgoing call.
69      */
70     public static final String KEY_INITIATING_USER = "initiating_user";
71 
72 
73     private final Context mContext;
74     private final CallsManager mCallsManager;
75     private final DefaultDialerCache mDefaultDialerCache;
76 
CallIntentProcessor(Context context, CallsManager callsManager, DefaultDialerCache defaultDialerCache)77     public CallIntentProcessor(Context context, CallsManager callsManager,
78             DefaultDialerCache defaultDialerCache) {
79         this.mContext = context;
80         this.mCallsManager = callsManager;
81         this.mDefaultDialerCache = defaultDialerCache;
82     }
83 
processIntent(Intent intent, String callingPackage)84     public void processIntent(Intent intent, String callingPackage) {
85         final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
86         Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
87 
88         Trace.beginSection("processNewCallCallIntent");
89         if (isUnknownCall) {
90             processUnknownCallIntent(mCallsManager, intent);
91         } else {
92             processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage,
93                     mDefaultDialerCache);
94         }
95         Trace.endSection();
96     }
97 
98 
99     /**
100      * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
101      *
102      * @param intent Call intent containing data about the handle to call.
103      * @param callingPackage The package which initiated the outgoing call (if known).
104      */
processOutgoingCallIntent( Context context, CallsManager callsManager, Intent intent, String callingPackage, DefaultDialerCache defaultDialerCache)105     static void processOutgoingCallIntent(
106             Context context,
107             CallsManager callsManager,
108             Intent intent,
109             String callingPackage,
110             DefaultDialerCache defaultDialerCache) {
111 
112         Uri handle = intent.getData();
113         String scheme = handle.getScheme();
114         String uriString = handle.getSchemeSpecificPart();
115 
116         // Ensure sip URIs dialed using TEL scheme get converted to SIP scheme.
117         if (PhoneAccount.SCHEME_TEL.equals(scheme) && PhoneNumberUtils.isUriNumber(uriString)) {
118             handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
119         }
120 
121         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
122                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
123 
124         Bundle clientExtras = null;
125         if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
126             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
127         }
128         if (clientExtras == null) {
129             clientExtras = new Bundle();
130         }
131 
132         if (intent.hasExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL)) {
133             clientExtras.putBoolean(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL,
134                     intent.getBooleanExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL,
135                             false));
136         }
137 
138         // Ensure call subject is passed on to the connection service.
139         if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) {
140             String callsubject = intent.getStringExtra(TelecomManager.EXTRA_CALL_SUBJECT);
141             clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject);
142         }
143 
144         final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
145                 VideoProfile.STATE_AUDIO_ONLY);
146         clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
147 
148         if (!callsManager.isSelfManaged(phoneAccountHandle,
149                 (UserHandle) intent.getParcelableExtra(KEY_INITIATING_USER))) {
150             boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
151             // Show the toast to warn user that it is a personal call though initiated in work
152             // profile.
153             if (fixedInitiatingUser) {
154                 Toast.makeText(context, Looper.getMainLooper(),
155                         context.getString(R.string.toast_personal_call_msg),
156                         Toast.LENGTH_LONG).show();
157             }
158         } else {
159             Log.i(CallIntentProcessor.class,
160                     "processOutgoingCallIntent: skip initiating user check");
161         }
162 
163         UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
164 
165         boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
166                 initiatingUser.getIdentifier());
167 
168         NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
169                 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
170                 isPrivilegedDialer, defaultDialerCache);
171 
172         // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
173         NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
174         if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
175             showErrorDialog(context, disposition.disconnectCause);
176             return;
177         }
178 
179         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
180         CompletableFuture<Call> callFuture = callsManager
181                 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
182                         intent, callingPackage);
183 
184         final Session logSubsession = Log.createSubsession();
185         callFuture.thenAccept((call) -> {
186             if (call != null) {
187                 Log.continueSession(logSubsession, "CIP.sNOCI");
188                 try {
189                     broadcaster.processCall(call, disposition);
190                 } finally {
191                     Log.endSession();
192                 }
193             }
194         });
195     }
196 
197     /**
198      * If the call is initiated from managed profile but there is no work dialer installed, treat
199      * the call is initiated from its parent user.
200      *
201      * @return whether the initiating user is fixed.
202      */
fixInitiatingUserIfNecessary(Context context, Intent intent)203     static boolean fixInitiatingUserIfNecessary(Context context, Intent intent) {
204         final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
205         if (UserUtil.isManagedProfile(context, initiatingUser)) {
206             boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context,
207                     initiatingUser.getIdentifier()).size() == 0;
208             if (noDialerInstalled) {
209                 final UserManager userManager = UserManager.get(context);
210                 UserHandle parentUserHandle =
211                         userManager.getProfileParent(
212                                 initiatingUser.getIdentifier()).getUserHandle();
213                 intent.putExtra(KEY_INITIATING_USER, parentUserHandle);
214 
215                 Log.i(CallIntentProcessor.class, "fixInitiatingUserIfNecessary: no dialer installed"
216                         + " for current user; setting initiator to parent %s" + parentUserHandle);
217                 return true;
218             }
219         }
220         return false;
221     }
222 
processIncomingCallIntent(CallsManager callsManager, Intent intent)223     static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
224         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
225                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
226 
227         if (phoneAccountHandle == null) {
228             Log.w(CallIntentProcessor.class,
229                     "Rejecting incoming call due to null phone account");
230             return;
231         }
232         if (phoneAccountHandle.getComponentName() == null) {
233             Log.w(CallIntentProcessor.class,
234                     "Rejecting incoming call due to null component name");
235             return;
236         }
237 
238         Bundle clientExtras = null;
239         if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
240             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
241         }
242         if (clientExtras == null) {
243             clientExtras = new Bundle();
244         }
245 
246         Log.d(CallIntentProcessor.class,
247                 "Processing incoming call from connection service [%s]",
248                 phoneAccountHandle.getComponentName());
249         callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
250     }
251 
processUnknownCallIntent(CallsManager callsManager, Intent intent)252     static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
253         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
254                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
255 
256         if (phoneAccountHandle == null) {
257             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
258             return;
259         }
260         if (phoneAccountHandle.getComponentName() == null) {
261             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
262             return;
263         }
264 
265         callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
266     }
267 
showErrorDialog(Context context, int errorCode)268     private static void showErrorDialog(Context context, int errorCode) {
269         final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
270         int errorMessageId = -1;
271         switch (errorCode) {
272             case DisconnectCause.INVALID_NUMBER:
273             case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
274                 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
275                 break;
276         }
277         if (errorMessageId != -1) {
278             errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
279             errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
280             context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
281         }
282     }
283 }
284