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