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 {
34         void processOutgoingCallIntent(Context context, CallsManager callsManager,
35                 Intent intent, String callingPackage);
36         void processIncomingCallIntent(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;
42         public AdapterImpl(DefaultDialerCache cache) {
43             mDefaultDialerCache = cache;
44         }
45 
46         @Override
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
54         public void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
55             CallIntentProcessor.processIncomingCallIntent(callsManager, intent);
56         }
57 
58         @Override
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 
77     public CallIntentProcessor(Context context, CallsManager callsManager,
78             DefaultDialerCache defaultDialerCache) {
79         this.mContext = context;
80         this.mCallsManager = callsManager;
81         this.mDefaultDialerCache = defaultDialerCache;
82     }
83 
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      */
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         if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_PRIORITY)) {
145             clientExtras.putInt(android.telecom.TelecomManager.EXTRA_PRIORITY, intent.getIntExtra(
146                     android.telecom.TelecomManager.EXTRA_PRIORITY,
147                             android.telecom.TelecomManager.PRIORITY_NORMAL));
148         }
149 
150         if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_LOCATION)) {
151             clientExtras.putParcelable(android.telecom.TelecomManager.EXTRA_LOCATION,
152                     intent.getParcelableExtra(android.telecom.TelecomManager.EXTRA_LOCATION));
153         }
154 
155         if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE)) {
156             clientExtras.putParcelable(android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE,
157                     intent.getParcelableExtra(
158                             android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE));
159         }
160 
161         final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
162                 VideoProfile.STATE_AUDIO_ONLY);
163         clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
164 
165         if (!callsManager.isSelfManaged(phoneAccountHandle,
166                 (UserHandle) intent.getParcelableExtra(KEY_INITIATING_USER))) {
167             boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
168             // Show the toast to warn user that it is a personal call though initiated in work
169             // profile.
170             if (fixedInitiatingUser) {
171                 Toast.makeText(context, Looper.getMainLooper(),
172                         context.getString(R.string.toast_personal_call_msg),
173                         Toast.LENGTH_LONG).show();
174             }
175         } else {
176             Log.i(CallIntentProcessor.class,
177                     "processOutgoingCallIntent: skip initiating user check");
178         }
179 
180         UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
181 
182         boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
183                 initiatingUser.getIdentifier());
184 
185         NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
186                 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
187                 isPrivilegedDialer, defaultDialerCache);
188 
189         // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
190         NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
191         if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
192             showErrorDialog(context, disposition.disconnectCause);
193             return;
194         }
195 
196         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
197         CompletableFuture<Call> callFuture = callsManager
198                 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
199                         intent, callingPackage);
200 
201         final Session logSubsession = Log.createSubsession();
202         callFuture.thenAccept((call) -> {
203             if (call != null) {
204                 Log.continueSession(logSubsession, "CIP.sNOCI");
205                 try {
206                     broadcaster.processCall(call, disposition);
207                 } finally {
208                     Log.endSession();
209                 }
210             }
211         });
212     }
213 
214     /**
215      * If the call is initiated from managed profile but there is no work dialer installed, treat
216      * the call is initiated from its parent user.
217      *
218      * @return whether the initiating user is fixed.
219      */
220     static boolean fixInitiatingUserIfNecessary(Context context, Intent intent) {
221         final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
222         if (UserUtil.isManagedProfile(context, initiatingUser)) {
223             boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context,
224                     initiatingUser.getIdentifier()).size() == 0;
225             if (noDialerInstalled) {
226                 final UserManager userManager = UserManager.get(context);
227                 UserHandle parentUserHandle =
228                         userManager.getProfileParent(
229                                 initiatingUser.getIdentifier()).getUserHandle();
230                 intent.putExtra(KEY_INITIATING_USER, parentUserHandle);
231 
232                 Log.i(CallIntentProcessor.class, "fixInitiatingUserIfNecessary: no dialer installed"
233                         + " for current user; setting initiator to parent %s" + parentUserHandle);
234                 return true;
235             }
236         }
237         return false;
238     }
239 
240     static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
241         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
242                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
243 
244         if (phoneAccountHandle == null) {
245             Log.w(CallIntentProcessor.class,
246                     "Rejecting incoming call due to null phone account");
247             return;
248         }
249         if (phoneAccountHandle.getComponentName() == null) {
250             Log.w(CallIntentProcessor.class,
251                     "Rejecting incoming call due to null component name");
252             return;
253         }
254 
255         Bundle clientExtras = null;
256         if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) {
257             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
258         }
259         if (clientExtras == null) {
260             clientExtras = new Bundle();
261         }
262 
263         Log.d(CallIntentProcessor.class,
264                 "Processing incoming call from connection service [%s]",
265                 phoneAccountHandle.getComponentName());
266         callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
267     }
268 
269     static void processUnknownCallIntent(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, "Rejecting unknown call due to null phone account");
275             return;
276         }
277         if (phoneAccountHandle.getComponentName() == null) {
278             Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
279             return;
280         }
281 
282         callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
283     }
284 
285     private static void showErrorDialog(Context context, int errorCode) {
286         final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
287         int errorMessageId = -1;
288         switch (errorCode) {
289             case DisconnectCause.INVALID_NUMBER:
290             case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
291                 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied;
292                 break;
293         }
294         if (errorMessageId != -1) {
295             errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
296             errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
297             context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
298         }
299     }
300 }
301