1 package com.android.server.telecom;
2 
3 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
4 
5 import com.android.internal.app.IntentForwarderActivity;
6 import com.android.server.telecom.components.ErrorDialogActivity;
7 import com.android.server.telecom.flags.FeatureFlags;
8 
9 import android.content.ActivityNotFoundException;
10 import android.content.Context;
11 import android.content.Intent;
12 import android.content.pm.PackageManager;
13 import android.content.pm.ResolveInfo;
14 import android.net.Uri;
15 import android.os.Bundle;
16 import android.os.Looper;
17 import android.os.Trace;
18 import android.os.UserHandle;
19 import android.os.UserManager;
20 import android.telecom.DefaultDialerManager;
21 import android.telecom.Log;
22 import android.telecom.Logging.Session;
23 import android.telecom.PhoneAccount;
24 import android.telecom.PhoneAccountHandle;
25 import android.telecom.TelecomManager;
26 import android.telecom.VideoProfile;
27 import android.telephony.DisconnectCause;
28 import android.telephony.PhoneNumberUtils;
29 import android.telephony.TelephonyManager;
30 import android.widget.Toast;
31 
32 import java.util.concurrent.CompletableFuture;
33 
34 /**
35  * Single point of entry for all outgoing and incoming calls.
36  * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
37  * captures call intents for individual users and forwards it to the {@link CallIntentProcessor}
38  * which interacts with the rest of Telecom, both of which run only as the primary user.
39  */
40 public class CallIntentProcessor {
41     public interface Adapter {
processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent, String callingPackage, FeatureFlags featureFlags)42         void processOutgoingCallIntent(Context context, CallsManager callsManager,
43                 Intent intent, String callingPackage, FeatureFlags featureFlags);
processIncomingCallIntent(CallsManager callsManager, Intent intent)44         void processIncomingCallIntent(CallsManager callsManager, Intent intent);
processUnknownCallIntent(CallsManager callsManager, Intent intent)45         void processUnknownCallIntent(CallsManager callsManager, Intent intent);
46     }
47 
48     public static class AdapterImpl implements Adapter {
49         private final DefaultDialerCache mDefaultDialerCache;
AdapterImpl(DefaultDialerCache cache)50         public AdapterImpl(DefaultDialerCache cache) {
51             mDefaultDialerCache = cache;
52         }
53 
54         @Override
processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent, String callingPackage, FeatureFlags featureFlags)55         public void processOutgoingCallIntent(Context context, CallsManager callsManager,
56                 Intent intent, String callingPackage, FeatureFlags featureFlags) {
57             CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent,
58                     callingPackage, mDefaultDialerCache, featureFlags);
59         }
60 
61         @Override
processIncomingCallIntent(CallsManager callsManager, Intent intent)62         public void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
63             CallIntentProcessor.processIncomingCallIntent(callsManager, intent);
64         }
65 
66         @Override
processUnknownCallIntent(CallsManager callsManager, Intent intent)67         public void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
68             CallIntentProcessor.processUnknownCallIntent(callsManager, intent);
69         }
70     }
71 
72     public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
73     public static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
74 
75     /**
76      * The user initiating the outgoing call.
77      */
78     public static final String KEY_INITIATING_USER = "initiating_user";
79 
80 
81     private final Context mContext;
82     private final CallsManager mCallsManager;
83     private final DefaultDialerCache mDefaultDialerCache;
84     private final FeatureFlags mFeatureFlags;
85 
CallIntentProcessor(Context context, CallsManager callsManager, DefaultDialerCache defaultDialerCache, FeatureFlags featureFlags)86     public CallIntentProcessor(Context context, CallsManager callsManager,
87             DefaultDialerCache defaultDialerCache, FeatureFlags featureFlags) {
88         this.mContext = context;
89         this.mCallsManager = callsManager;
90         this.mDefaultDialerCache = defaultDialerCache;
91         this.mFeatureFlags = featureFlags;
92     }
93 
processIntent(Intent intent, String callingPackage)94     public void processIntent(Intent intent, String callingPackage) {
95         final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
96         Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
97 
98         Trace.beginSection("processNewCallCallIntent");
99         if (isUnknownCall) {
100             processUnknownCallIntent(mCallsManager, intent);
101         } else {
102             processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage,
103                     mDefaultDialerCache, mFeatureFlags);
104         }
105         Trace.endSection();
106     }
107 
108 
109     /**
110      * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
111      *
112      * @param intent Call intent containing data about the handle to call.
113      * @param callingPackage The package which initiated the outgoing call (if known).
114      */
processOutgoingCallIntent( Context context, CallsManager callsManager, Intent intent, String callingPackage, DefaultDialerCache defaultDialerCache, FeatureFlags featureFlags)115     static void processOutgoingCallIntent(
116             Context context,
117             CallsManager callsManager,
118             Intent intent,
119             String callingPackage,
120             DefaultDialerCache defaultDialerCache,
121             FeatureFlags featureFlags) {
122 
123         Uri handle = intent.getData();
124         String scheme = handle.getScheme();
125         String uriString = handle.getSchemeSpecificPart();
126 
127         // Ensure sip URIs dialed using TEL scheme get converted to SIP scheme.
128         if (PhoneAccount.SCHEME_TEL.equals(scheme) && PhoneNumberUtils.isUriNumber(uriString)) {
129             handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
130         }
131 
132         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
133                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
134 
135         Bundle clientExtras = null;
136         if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
137             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
138         }
139         if (clientExtras == null) {
140             clientExtras = new Bundle();
141         }
142 
143         if (intent.hasExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL)) {
144             clientExtras.putBoolean(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL,
145                     intent.getBooleanExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL,
146                             false));
147         }
148 
149         // Ensure call subject is passed on to the connection service.
150         if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) {
151             String callsubject = intent.getStringExtra(TelecomManager.EXTRA_CALL_SUBJECT);
152             clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject);
153         }
154 
155         if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_PRIORITY)) {
156             clientExtras.putInt(android.telecom.TelecomManager.EXTRA_PRIORITY, intent.getIntExtra(
157                     android.telecom.TelecomManager.EXTRA_PRIORITY,
158                             android.telecom.TelecomManager.PRIORITY_NORMAL));
159         }
160 
161         if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_LOCATION)) {
162             clientExtras.putParcelable(android.telecom.TelecomManager.EXTRA_LOCATION,
163                     intent.getParcelableExtra(android.telecom.TelecomManager.EXTRA_LOCATION));
164         }
165 
166         if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE)) {
167             clientExtras.putParcelable(android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE,
168                     intent.getParcelableExtra(
169                             android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE));
170         }
171 
172         final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
173                 VideoProfile.STATE_AUDIO_ONLY);
174         clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
175 
176         if (!callsManager.isSelfManaged(phoneAccountHandle,
177                 (UserHandle) intent.getParcelableExtra(KEY_INITIATING_USER))) {
178             boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(
179                     context, intent, featureFlags);
180             // Show the toast to warn user that it is a personal call though initiated in work
181             // profile.
182             if (fixedInitiatingUser) {
183                 if (featureFlags.telecomResolveHiddenDependencies()) {
184                     context.getMainExecutor().execute(() ->
185                             Toast.makeText(context, context.getString(
186                                     R.string.toast_personal_call_msg), Toast.LENGTH_LONG).show());
187                 } else {
188                     Toast.makeText(context, Looper.getMainLooper(),
189                             context.getString(R.string.toast_personal_call_msg),
190                             Toast.LENGTH_LONG).show();
191                 }
192             }
193         } else {
194             Log.i(CallIntentProcessor.class,
195                     "processOutgoingCallIntent: skip initiating user check");
196         }
197 
198         UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
199 
200         boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
201                 initiatingUser.getIdentifier());
202 
203         if (privateSpaceFlagsEnabled()) {
204             if (!callsManager.isSelfManaged(phoneAccountHandle, initiatingUser)
205                     && !TelephonyUtil.shouldProcessAsEmergency(context, handle)
206                     && UserUtil.isPrivateProfile(initiatingUser, context)) {
207                 boolean dialogShown = maybeRedirectToIntentForwarderForPrivate(context, intent,
208                         initiatingUser);
209                 if (dialogShown) {
210                     return;
211                 }
212             }
213         }
214 
215         NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
216                 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
217                 isPrivilegedDialer, defaultDialerCache, new MmiUtils(), featureFlags);
218 
219         // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
220         NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
221         if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
222             showErrorDialog(context, disposition.disconnectCause);
223             return;
224         }
225 
226         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
227         CompletableFuture<Call> callFuture = callsManager
228                 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
229                         intent, callingPackage);
230 
231         final Session logSubsession = Log.createSubsession();
232         callFuture.thenAccept((call) -> {
233             if (call != null) {
234                 Log.continueSession(logSubsession, "CIP.sNOCI");
235                 try {
236                     broadcaster.processCall(call, disposition);
237                 } finally {
238                     Log.endSession();
239                 }
240             }
241         });
242     }
243 
244     /**
245      * If the call is initiated from managed profile but there is no work dialer installed, treat
246      * the call is initiated from its parent user.
247      *
248      * @return whether the initiating user is fixed.
249      */
fixInitiatingUserIfNecessary(Context context, Intent intent, FeatureFlags featureFlags)250     static boolean fixInitiatingUserIfNecessary(Context context, Intent intent,
251             FeatureFlags featureFlags) {
252         final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
253         if (UserUtil.isManagedProfile(context, initiatingUser, featureFlags)) {
254             boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context,
255                     initiatingUser.getIdentifier()).size() == 0;
256             if (noDialerInstalled) {
257                 final UserManager userManager = context.getSystemService(UserManager.class);
258                 UserHandle parentUserHandle = featureFlags.telecomResolveHiddenDependencies()
259                         ? userManager.getProfileParent(initiatingUser)
260                         : userManager.getProfileParent(initiatingUser.getIdentifier())
261                                 .getUserHandle();
262                 intent.putExtra(KEY_INITIATING_USER, parentUserHandle);
263 
264                 Log.i(CallIntentProcessor.class, "fixInitiatingUserIfNecessary: no dialer installed"
265                         + " for current user; setting initiator to parent %s" + parentUserHandle);
266                 return true;
267             }
268         }
269         return false;
270     }
271 
processIncomingCallIntent(CallsManager callsManager, Intent intent)272     static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
273         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
274                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
275 
276         if (phoneAccountHandle == null) {
277             Log.w(CallIntentProcessor.class,
278                     "Rejecting incoming call due to null phone account");
279             return;
280         }
281         if (phoneAccountHandle.getComponentName() == null) {
282             Log.w(CallIntentProcessor.class,
283                     "Rejecting incoming call due to null component name");
284             return;
285         }
286 
287         Bundle clientExtras = null;
288         if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
289             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
290         }
291         if (clientExtras == null) {
292             clientExtras = new Bundle();
293         }
294 
295         Log.d(CallIntentProcessor.class,
296                 "Processing incoming call from connection service [%s]",
297                 phoneAccountHandle.getComponentName());
298         callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
299     }
300 
processUnknownCallIntent(CallsManager callsManager, Intent intent)301     static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
302         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
303                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
304 
305         if (phoneAccountHandle == null) {
306             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
307             return;
308         }
309         if (phoneAccountHandle.getComponentName() == null) {
310             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
311             return;
312         }
313 
314         callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
315     }
316 
showErrorDialog(Context context, int errorCode)317     private static void showErrorDialog(Context context, int errorCode) {
318         final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
319         int errorMessageId = -1;
320         switch (errorCode) {
321             case DisconnectCause.INVALID_NUMBER:
322             case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
323                 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
324                 break;
325         }
326         if (errorMessageId != -1) {
327             errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
328             errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
329             context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
330         }
331     }
332 
privateSpaceFlagsEnabled()333     private static boolean privateSpaceFlagsEnabled() {
334         return android.multiuser.Flags.enablePrivateSpaceFeatures()
335                 && android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
336     }
337 
maybeRedirectToIntentForwarderForPrivate( Context context, Intent forwardCallIntent, UserHandle initiatingUser)338     private static boolean maybeRedirectToIntentForwarderForPrivate(
339             Context context,
340             Intent forwardCallIntent,
341             UserHandle initiatingUser) {
342 
343         // If CALL intent filters are set to SKIP_CURRENT_PROFILE, PM will resolve this to an
344         // intent forwarder activity.
345         forwardCallIntent.setComponent(null);
346         forwardCallIntent.setPackage(null);
347         ResolveInfo resolveInfos =
348                 context.getPackageManager()
349                         .resolveActivityAsUser(
350                                 forwardCallIntent,
351                                 PackageManager.ResolveInfoFlags.of(MATCH_DEFAULT_ONLY),
352                                 initiatingUser.getIdentifier());
353 
354         if (resolveInfos == null
355                 || !resolveInfos
356                 .getComponentInfo()
357                 .getComponentName()
358                 .getShortClassName()
359                 .equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)) {
360             return false;
361         }
362 
363         try {
364             context.startActivityAsUser(forwardCallIntent, initiatingUser);
365             return true;
366         } catch (ActivityNotFoundException e) {
367             Log.e(CallIntentProcessor.class, e, "Unable to start call intent in the main user");
368             return false;
369         }
370     }
371 }
372