• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION.SDK_INT;
4 import static android.os.Build.VERSION_CODES.LOLLIPOP;
5 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
6 import static android.os.Build.VERSION_CODES.M;
7 import static android.os.Build.VERSION_CODES.N;
8 import static android.os.Build.VERSION_CODES.O;
9 import static android.os.Build.VERSION_CODES.R;
10 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
11 import static com.google.common.base.Verify.verifyNotNull;
12 
13 import android.annotation.Nullable;
14 import android.annotation.SystemApi;
15 import android.annotation.TargetApi;
16 import android.bluetooth.BluetoothDevice;
17 import android.content.ComponentName;
18 import android.content.Context;
19 import android.content.Intent;
20 import android.content.pm.ResolveInfo;
21 import android.net.Uri;
22 import android.os.Bundle;
23 import android.telecom.CallAudioState;
24 import android.telecom.Connection;
25 import android.telecom.ConnectionRequest;
26 import android.telecom.ConnectionService;
27 import android.telecom.PhoneAccount;
28 import android.telecom.PhoneAccountHandle;
29 import android.telecom.TelecomManager;
30 import android.telecom.VideoProfile;
31 import android.text.TextUtils;
32 import android.util.ArrayMap;
33 import com.google.common.collect.ImmutableList;
34 import com.google.common.collect.Iterables;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.HashSet;
38 import java.util.LinkedHashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import org.robolectric.android.controller.ServiceController;
43 import org.robolectric.annotation.HiddenApi;
44 import org.robolectric.annotation.Implementation;
45 import org.robolectric.annotation.Implements;
46 import org.robolectric.annotation.RealObject;
47 import org.robolectric.util.ReflectionHelpers;
48 
49 @Implements(value = TelecomManager.class, minSdk = LOLLIPOP)
50 public class ShadowTelecomManager {
51 
52   /**
53    * Mode describing how the shadow handles incoming ({@link TelecomManager#addNewIncomingCall}) and
54    * outgoing ({@link TelecomManager#placeCall}) call requests.
55    */
56   public enum CallRequestMode {
57     /** Automatically allows all call requests. */
58     ALLOW_ALL,
59 
60     /** Automatically denies all call requests. */
61     DENY_ALL,
62 
63     /**
64      * Do not automatically allow or deny any call requests. Instead, call requests should be
65      * allowed or denied manually by calling the following methods:
66      *
67      * <ul>
68      *   <li>{@link #allowIncomingCall(IncomingCallRecord)}
69      *   <li>{@link #denyIncomingCall(IncomingCallRecord)}
70      *   <li>{@link #allowOutgoingCall(OutgoingCallRecord)}
71      *   <li>{@link #denyOutgoingCall(OutgoingCallRecord)}
72      * </ul>
73      */
74     MANUAL,
75   }
76 
77   @RealObject
78   private TelecomManager realObject;
79 
80   private final LinkedHashMap<PhoneAccountHandle, PhoneAccount> accounts = new LinkedHashMap<>();
81   private final LinkedHashMap<PhoneAccountHandle, String> voicemailNumbers = new LinkedHashMap<>();
82   private final LinkedHashMap<PhoneAccountHandle, String> line1Numbers = new LinkedHashMap<>();
83 
84   private final List<IncomingCallRecord> incomingCalls = new ArrayList<>();
85   private final List<OutgoingCallRecord> outgoingCalls = new ArrayList<>();
86   private final List<UnknownCallRecord> unknownCalls = new ArrayList<>();
87   private final Map<String, PhoneAccountHandle> defaultOutgoingPhoneAccounts = new ArrayMap<>();
88   private Intent manageBlockNumbersIntent;
89   private CallRequestMode callRequestMode = CallRequestMode.MANUAL;
90   private PhoneAccountHandle simCallManager;
91   private String defaultDialerPackageName;
92   private String systemDefaultDialerPackageName;
93   private boolean isInCall;
94   private boolean ttySupported;
95   private PhoneAccountHandle userSelectedOutgoingPhoneAccount;
96   private boolean readPhoneStatePermission = true;
97   private boolean callPhonePermission = true;
98   private boolean handleMmiValue = false;
99   private ConnectionService connectionService;
100   private boolean isOutgoingCallPermitted = false;
101 
getCallRequestMode()102   public CallRequestMode getCallRequestMode() {
103     return callRequestMode;
104   }
105 
setCallRequestMode(CallRequestMode callRequestMode)106   public void setCallRequestMode(CallRequestMode callRequestMode) {
107     this.callRequestMode = callRequestMode;
108   }
109 
110   /**
111    * Set default outgoing phone account to be returned from {@link
112    * #getDefaultOutgoingPhoneAccount(String)} for corresponding {@code uriScheme}.
113    */
setDefaultOutgoingPhoneAccount(String uriScheme, PhoneAccountHandle handle)114   public void setDefaultOutgoingPhoneAccount(String uriScheme, PhoneAccountHandle handle) {
115     defaultOutgoingPhoneAccounts.put(uriScheme, handle);
116   }
117 
118   /** Remove default outgoing phone account for corresponding {@code uriScheme}. */
removeDefaultOutgoingPhoneAccount(String uriScheme)119   public void removeDefaultOutgoingPhoneAccount(String uriScheme) {
120     defaultOutgoingPhoneAccounts.remove(uriScheme);
121   }
122 
123   /** Sets the result of {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)}. */
setIsOutgoingCallPermitted(boolean isOutgoingCallPermitted)124   public void setIsOutgoingCallPermitted(boolean isOutgoingCallPermitted) {
125     this.isOutgoingCallPermitted = isOutgoingCallPermitted;
126   }
127 
128   /**
129    * Returns default outgoing phone account set through {@link
130    * #setDefaultOutgoingPhoneAccount(String, PhoneAccountHandle)} for corresponding {@code
131    * uriScheme}.
132    */
133   @Implementation
getDefaultOutgoingPhoneAccount(String uriScheme)134   protected PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
135     return defaultOutgoingPhoneAccounts.get(uriScheme);
136   }
137 
138   @Implementation
139   @HiddenApi
getUserSelectedOutgoingPhoneAccount()140   public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
141     return userSelectedOutgoingPhoneAccount;
142   }
143 
144   @Implementation
145   @HiddenApi
setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle)146   public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
147     userSelectedOutgoingPhoneAccount = accountHandle;
148   }
149 
150   @Implementation
getSimCallManager()151   protected PhoneAccountHandle getSimCallManager() {
152     return simCallManager;
153   }
154 
155   @Implementation(minSdk = M)
156   @HiddenApi
getSimCallManager(int userId)157   public PhoneAccountHandle getSimCallManager(int userId) {
158     return null;
159   }
160 
161   @Implementation
162   @HiddenApi
getConnectionManager()163   public PhoneAccountHandle getConnectionManager() {
164     return this.getSimCallManager();
165   }
166 
167   @Implementation
168   @HiddenApi
getPhoneAccountsSupportingScheme(String uriScheme)169   public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
170     List<PhoneAccountHandle> result = new ArrayList<>();
171 
172     for (PhoneAccountHandle handle : accounts.keySet()) {
173       PhoneAccount phoneAccount = accounts.get(handle);
174       if (phoneAccount.getSupportedUriSchemes().contains(uriScheme)) {
175         result.add(handle);
176       }
177     }
178     return result;
179   }
180 
181   @Implementation(minSdk = M)
getCallCapablePhoneAccounts()182   protected List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
183     checkReadPhoneStatePermission();
184     return this.getCallCapablePhoneAccounts(false);
185   }
186 
187   @Implementation(minSdk = M)
188   @HiddenApi
getCallCapablePhoneAccounts(boolean includeDisabledAccounts)189   public List<PhoneAccountHandle> getCallCapablePhoneAccounts(boolean includeDisabledAccounts) {
190     List<PhoneAccountHandle> result = new ArrayList<>();
191 
192     for (PhoneAccountHandle handle : accounts.keySet()) {
193       PhoneAccount phoneAccount = accounts.get(handle);
194       if (!phoneAccount.isEnabled() && !includeDisabledAccounts) {
195         continue;
196       }
197       result.add(handle);
198     }
199     return result;
200   }
201 
202   @Implementation(minSdk = O)
getSelfManagedPhoneAccounts()203   public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
204     List<PhoneAccountHandle> result = new ArrayList<>();
205 
206     for (PhoneAccountHandle handle : accounts.keySet()) {
207       PhoneAccount phoneAccount = accounts.get(handle);
208       if ((phoneAccount.getCapabilities() & PhoneAccount.CAPABILITY_SELF_MANAGED)
209           == PhoneAccount.CAPABILITY_SELF_MANAGED) {
210         result.add(handle);
211       }
212     }
213     return result;
214   }
215 
216   @Implementation
217   @HiddenApi
getPhoneAccountsForPackage()218   public List<PhoneAccountHandle> getPhoneAccountsForPackage() {
219     Context context = ReflectionHelpers.getField(realObject, "mContext");
220 
221     List<PhoneAccountHandle> results = new ArrayList<>();
222     for (PhoneAccountHandle handle : accounts.keySet()) {
223       if (handle.getComponentName().getPackageName().equals(context.getPackageName())) {
224         results.add(handle);
225       }
226     }
227     return results;
228   }
229 
230   @Implementation
getPhoneAccount(PhoneAccountHandle account)231   protected PhoneAccount getPhoneAccount(PhoneAccountHandle account) {
232     checkReadPhoneStatePermission();
233     return accounts.get(account);
234   }
235 
236   @Implementation
237   @HiddenApi
getAllPhoneAccountsCount()238   public int getAllPhoneAccountsCount() {
239     return accounts.size();
240   }
241 
242   @Implementation
243   @HiddenApi
getAllPhoneAccounts()244   public List<PhoneAccount> getAllPhoneAccounts() {
245     return ImmutableList.copyOf(accounts.values());
246   }
247 
248   @Implementation
249   @HiddenApi
getAllPhoneAccountHandles()250   public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
251     return ImmutableList.copyOf(accounts.keySet());
252   }
253 
254   @Implementation
registerPhoneAccount(PhoneAccount account)255   protected void registerPhoneAccount(PhoneAccount account) {
256     account = adjustCapabilities(account);
257     accounts.put(account.getAccountHandle(), account);
258   }
259 
adjustCapabilities(PhoneAccount account)260   private PhoneAccount adjustCapabilities(PhoneAccount account) {
261     // Mirror the capabilities adjustments done in com.android.server.telecom.PhoneAccountRegistrar.
262     if (SDK_INT >= UPSIDE_DOWN_CAKE
263         && account.hasCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
264         && !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
265       return account.toBuilder()
266           .setCapabilities(account.getCapabilities() | PhoneAccount.CAPABILITY_SELF_MANAGED)
267           .build();
268     }
269     return account;
270   }
271 
272   @Implementation
unregisterPhoneAccount(PhoneAccountHandle accountHandle)273   protected void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
274     accounts.remove(accountHandle);
275   }
276 
277   /** @deprecated */
278   @Deprecated
279   @Implementation
280   @HiddenApi
clearAccounts()281   public void clearAccounts() {
282     accounts.clear();
283   }
284 
285   @Implementation(minSdk = LOLLIPOP_MR1)
286   @HiddenApi
clearAccountsForPackage(String packageName)287   public void clearAccountsForPackage(String packageName) {
288     Set<PhoneAccountHandle> phoneAccountHandlesInPackage = new HashSet<>();
289 
290     for (PhoneAccountHandle handle : accounts.keySet()) {
291       if (handle.getComponentName().getPackageName().equals(packageName)) {
292         phoneAccountHandlesInPackage.add(handle);
293       }
294     }
295 
296     for (PhoneAccountHandle handle : phoneAccountHandlesInPackage) {
297       accounts.remove(handle);
298     }
299   }
300 
301   /** @deprecated */
302   @Deprecated
303   @Implementation
304   @HiddenApi
getDefaultPhoneApp()305   public ComponentName getDefaultPhoneApp() {
306     return null;
307   }
308 
309   @Implementation(minSdk = M)
getDefaultDialerPackage()310   protected String getDefaultDialerPackage() {
311     return defaultDialerPackageName;
312   }
313 
314   /** @deprecated API deprecated since Q, for testing, use setDefaultDialerPackage instead */
315   @Deprecated
316   @Implementation(minSdk = M)
317   @HiddenApi
setDefaultDialer(String packageName)318   public boolean setDefaultDialer(String packageName) {
319     this.defaultDialerPackageName = packageName;
320     return true;
321   }
322 
323   /** Set returned value of {@link #getDefaultDialerPackage()}. */
setDefaultDialerPackage(String packageName)324   public void setDefaultDialerPackage(String packageName) {
325     this.defaultDialerPackageName = packageName;
326   }
327 
328   @Implementation(minSdk = M)
329   @HiddenApi // API goes public in Q
getSystemDialerPackage()330   protected String getSystemDialerPackage() {
331     return systemDefaultDialerPackageName;
332   }
333 
334   /** Set returned value of {@link #getSystemDialerPackage()}. */
setSystemDialerPackage(String packageName)335   public void setSystemDialerPackage(String packageName) {
336     this.systemDefaultDialerPackageName = packageName;
337   }
338 
setVoicemailNumber(PhoneAccountHandle accountHandle, String number)339   public void setVoicemailNumber(PhoneAccountHandle accountHandle, String number) {
340     voicemailNumbers.put(accountHandle, number);
341   }
342 
343   @Implementation(minSdk = M)
isVoiceMailNumber(PhoneAccountHandle accountHandle, String number)344   protected boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) {
345     return TextUtils.equals(number, voicemailNumbers.get(accountHandle));
346   }
347 
348   @Implementation(minSdk = M)
getVoiceMailNumber(PhoneAccountHandle accountHandle)349   protected String getVoiceMailNumber(PhoneAccountHandle accountHandle) {
350     return voicemailNumbers.get(accountHandle);
351   }
352 
353   @Implementation(minSdk = LOLLIPOP_MR1)
getLine1Number(PhoneAccountHandle accountHandle)354   protected String getLine1Number(PhoneAccountHandle accountHandle) {
355     checkReadPhoneStatePermission();
356     return line1Numbers.get(accountHandle);
357   }
358 
setLine1Number(PhoneAccountHandle accountHandle, String number)359   public void setLine1Number(PhoneAccountHandle accountHandle, String number) {
360     line1Numbers.put(accountHandle, number);
361   }
362 
363   /** Sets the return value for {@link TelecomManager#isInCall}. */
setIsInCall(boolean isInCall)364   public void setIsInCall(boolean isInCall) {
365     this.isInCall = isInCall;
366   }
367 
368   /**
369    * Overrides behavior of {@link TelecomManager#isInCall} to return pre-set result.
370    *
371    * @return Value set by calling {@link ShadowTelecomManager#setIsInCall}. If setIsInCall has not
372    *     previously been called, will return false.
373    */
374   @Implementation
isInCall()375   protected boolean isInCall() {
376     return isInCall;
377   }
378 
379   @Implementation
380   @HiddenApi
getCallState()381   public int getCallState() {
382     return 0;
383   }
384 
385   @Implementation
386   @HiddenApi
isRinging()387   public boolean isRinging() {
388     for (IncomingCallRecord callRecord : incomingCalls) {
389       if (callRecord.isRinging) {
390         return true;
391       }
392     }
393     for (UnknownCallRecord callRecord : unknownCalls) {
394       if (callRecord.isRinging) {
395         return true;
396       }
397     }
398     return false;
399   }
400 
401   @Implementation
402   @HiddenApi
endCall()403   public boolean endCall() {
404     return false;
405   }
406 
407   @Implementation
acceptRingingCall()408   protected void acceptRingingCall() {}
409 
410   @Implementation
silenceRinger()411   protected void silenceRinger() {
412     for (IncomingCallRecord callRecord : incomingCalls) {
413       callRecord.isRinging = false;
414     }
415     for (UnknownCallRecord callRecord : unknownCalls) {
416       callRecord.isRinging = false;
417     }
418   }
419 
420   @Implementation
isTtySupported()421   protected boolean isTtySupported() {
422     checkReadPhoneStatePermission();
423     return ttySupported;
424   }
425 
426   /** Sets the value to be returned by {@link #isTtySupported()}. */
setTtySupported(boolean isSupported)427   public void setTtySupported(boolean isSupported) {
428     ttySupported = isSupported;
429   }
430 
431   @Implementation
432   @HiddenApi
getCurrentTtyMode()433   public int getCurrentTtyMode() {
434     return 0;
435   }
436 
437   @Implementation
addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras)438   protected void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) {
439     IncomingCallRecord call = new IncomingCallRecord(phoneAccount, extras);
440     incomingCalls.add(call);
441 
442     switch (callRequestMode) {
443       case ALLOW_ALL:
444         allowIncomingCall(call);
445         break;
446       case DENY_ALL:
447         denyIncomingCall(call);
448         break;
449       default:
450         // Do nothing.
451     }
452   }
453 
getAllIncomingCalls()454   public List<IncomingCallRecord> getAllIncomingCalls() {
455     return ImmutableList.copyOf(incomingCalls);
456   }
457 
getLastIncomingCall()458   public IncomingCallRecord getLastIncomingCall() {
459     return Iterables.getLast(incomingCalls);
460   }
461 
getOnlyIncomingCall()462   public IncomingCallRecord getOnlyIncomingCall() {
463     return Iterables.getOnlyElement(incomingCalls);
464   }
465 
466   /**
467    * Allows an {@link IncomingCallRecord} created via {@link TelecomManager#addNewIncomingCall}.
468    *
469    * <p>Specifically, this method sets up the relevant {@link ConnectionService} and returns the
470    * result of {@link ConnectionService#onCreateIncomingConnection}.
471    */
472   @TargetApi(M)
473   @Nullable
allowIncomingCall(IncomingCallRecord call)474   public Connection allowIncomingCall(IncomingCallRecord call) {
475     if (call.isHandled) {
476       throw new IllegalStateException("Call has already been allowed or denied.");
477     }
478     call.isHandled = true;
479 
480     PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount);
481     ConnectionRequest request = buildConnectionRequestForIncomingCall(call);
482     ConnectionService service = getConnectionService(phoneAccount);
483     return service.onCreateIncomingConnection(phoneAccount, request);
484   }
485 
486   /**
487    * Denies an {@link IncomingCallRecord} created via {@link TelecomManager#addNewIncomingCall}.
488    *
489    * <p>Specifically, this method sets up the relevant {@link ConnectionService} and calls {@link
490    * ConnectionService#onCreateIncomingConnectionFailed}.
491    */
492   @TargetApi(O)
denyIncomingCall(IncomingCallRecord call)493   public void denyIncomingCall(IncomingCallRecord call) {
494     if (call.isHandled) {
495       throw new IllegalStateException("Call has already been allowed or denied.");
496     }
497     call.isHandled = true;
498 
499     PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount);
500     ConnectionRequest request = buildConnectionRequestForIncomingCall(call);
501     ConnectionService service = getConnectionService(phoneAccount);
502     service.onCreateIncomingConnectionFailed(phoneAccount, request);
503   }
504 
buildConnectionRequestForIncomingCall(IncomingCallRecord call)505   private static ConnectionRequest buildConnectionRequestForIncomingCall(IncomingCallRecord call) {
506     PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount);
507     Bundle extras = verifyNotNull(call.extras);
508     Uri address = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
509     int videoState =
510         extras.getInt(
511             TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY);
512     return new ConnectionRequest(phoneAccount, address, new Bundle(extras), videoState);
513   }
514 
515   @Implementation(minSdk = M)
placeCall(Uri address, Bundle extras)516   protected void placeCall(Uri address, Bundle extras) {
517     checkCallPhonePermission();
518     OutgoingCallRecord call = new OutgoingCallRecord(address, extras);
519     outgoingCalls.add(call);
520 
521     switch (callRequestMode) {
522       case ALLOW_ALL:
523         allowOutgoingCall(call);
524         break;
525       case DENY_ALL:
526         denyOutgoingCall(call);
527         break;
528       default:
529         // Do nothing.
530     }
531   }
532 
getAllOutgoingCalls()533   public List<OutgoingCallRecord> getAllOutgoingCalls() {
534     return ImmutableList.copyOf(outgoingCalls);
535   }
536 
getLastOutgoingCall()537   public OutgoingCallRecord getLastOutgoingCall() {
538     return Iterables.getLast(outgoingCalls);
539   }
540 
getOnlyOutgoingCall()541   public OutgoingCallRecord getOnlyOutgoingCall() {
542     return Iterables.getOnlyElement(outgoingCalls);
543   }
544 
545   /**
546    * Allows an {@link OutgoingCallRecord} created via {@link TelecomManager#placeCall}.
547    *
548    * <p>Specifically, this method sets up the relevant {@link ConnectionService} and returns the
549    * result of {@link ConnectionService#onCreateOutgoingConnection}.
550    */
551   @TargetApi(M)
552   @Nullable
allowOutgoingCall(OutgoingCallRecord call)553   public Connection allowOutgoingCall(OutgoingCallRecord call) {
554     if (call.isHandled) {
555       throw new IllegalStateException("Call has already been allowed or denied.");
556     }
557     call.isHandled = true;
558 
559     PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount);
560     ConnectionRequest request = buildConnectionRequestForOutgoingCall(call);
561     ConnectionService service = getConnectionService(phoneAccount);
562     return service.onCreateOutgoingConnection(phoneAccount, request);
563   }
564 
565   /**
566    * Denies an {@link OutgoingCallRecord} created via {@link TelecomManager#placeCall}.
567    *
568    * <p>Specifically, this method sets up the relevant {@link ConnectionService} and calls {@link
569    * ConnectionService#onCreateOutgoingConnectionFailed}.
570    */
571   @TargetApi(O)
denyOutgoingCall(OutgoingCallRecord call)572   public void denyOutgoingCall(OutgoingCallRecord call) {
573     if (call.isHandled) {
574       throw new IllegalStateException("Call has already been allowed or denied.");
575     }
576     call.isHandled = true;
577 
578     PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount);
579     ConnectionRequest request = buildConnectionRequestForOutgoingCall(call);
580     ConnectionService service = getConnectionService(phoneAccount);
581     service.onCreateOutgoingConnectionFailed(phoneAccount, request);
582   }
583 
buildConnectionRequestForOutgoingCall(OutgoingCallRecord call)584   private static ConnectionRequest buildConnectionRequestForOutgoingCall(OutgoingCallRecord call) {
585     PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount);
586     Uri address = verifyNotNull(call.address);
587     Bundle extras = verifyNotNull(call.extras);
588     Bundle outgoingCallExtras = extras.getBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
589     int videoState =
590         extras.getInt(
591             TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY);
592     return new ConnectionRequest(
593         phoneAccount,
594         address,
595         outgoingCallExtras == null ? null : new Bundle(outgoingCallExtras),
596         videoState);
597   }
598 
599   @Implementation
600   @HiddenApi
addNewUnknownCall(PhoneAccountHandle phoneAccount, Bundle extras)601   public void addNewUnknownCall(PhoneAccountHandle phoneAccount, Bundle extras) {
602     unknownCalls.add(new UnknownCallRecord(phoneAccount, extras));
603   }
604 
getAllUnknownCalls()605   public List<UnknownCallRecord> getAllUnknownCalls() {
606     return ImmutableList.copyOf(unknownCalls);
607   }
608 
getLastUnknownCall()609   public UnknownCallRecord getLastUnknownCall() {
610     return Iterables.getLast(unknownCalls);
611   }
612 
getOnlyUnknownCall()613   public UnknownCallRecord getOnlyUnknownCall() {
614     return Iterables.getOnlyElement(unknownCalls);
615   }
616 
617   /**
618    * Set connection service.
619    *
620    * <p>This method can be used in case, when you already created connection service and would like
621    * to use it in telecom manager instead of creating new one.
622    *
623    * @param service existing connection service
624    */
setConnectionService(ConnectionService service)625   public void setConnectionService(ConnectionService service) {
626     connectionService = service;
627   }
628 
getConnectionService(PhoneAccountHandle phoneAccount)629   private ConnectionService getConnectionService(PhoneAccountHandle phoneAccount) {
630     if (connectionService == null) {
631       connectionService = setupConnectionService(phoneAccount);
632     }
633     return connectionService;
634   }
635 
setupConnectionService(PhoneAccountHandle phoneAccount)636   private static ConnectionService setupConnectionService(PhoneAccountHandle phoneAccount) {
637     ComponentName service = phoneAccount.getComponentName();
638     Class<? extends ConnectionService> clazz;
639     try {
640       clazz = Class.forName(service.getClassName()).asSubclass(ConnectionService.class);
641     } catch (ClassNotFoundException e) {
642       throw new IllegalArgumentException(e);
643     }
644     return verifyNotNull(
645         ServiceController.of(ReflectionHelpers.callConstructor(clazz), null).create().get());
646   }
647 
setHandleMmiValue(boolean handleMmiValue)648   public void setHandleMmiValue(boolean handleMmiValue) {
649     this.handleMmiValue = handleMmiValue;
650   }
651 
652   @Implementation
handleMmi(String dialString)653   protected boolean handleMmi(String dialString) {
654     return handleMmiValue;
655   }
656 
657   @Implementation(minSdk = M)
handleMmi(String dialString, PhoneAccountHandle accountHandle)658   protected boolean handleMmi(String dialString, PhoneAccountHandle accountHandle) {
659     return handleMmiValue;
660   }
661 
662   @Implementation(minSdk = LOLLIPOP_MR1)
getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle)663   protected Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
664     return Uri.parse("content://icc/adn");
665   }
666 
667   @Implementation
cancelMissedCallsNotification()668   protected void cancelMissedCallsNotification() {}
669 
670   @Implementation
showInCallScreen(boolean showDialpad)671   protected void showInCallScreen(boolean showDialpad) {}
672 
673   @Implementation(minSdk = M)
674   @HiddenApi
enablePhoneAccount(PhoneAccountHandle handle, boolean isEnabled)675   public void enablePhoneAccount(PhoneAccountHandle handle, boolean isEnabled) {
676     if (getPhoneAccount(handle) == null) {
677       return;
678     }
679     getPhoneAccount(handle).setIsEnabled(isEnabled);
680   }
681 
682   /**
683    * Returns the intent set by {@link ShadowTelecomManager#setManageBlockNumbersIntent(Intent)} ()}
684    */
685   @Implementation(minSdk = N)
createManageBlockedNumbersIntent()686   protected Intent createManageBlockedNumbersIntent() {
687     return this.manageBlockNumbersIntent;
688   }
689 
690   /**
691    * Sets the BlockNumbersIntent to be returned by {@link
692    * ShadowTelecomManager#createManageBlockedNumbersIntent()}
693    */
setManageBlockNumbersIntent(Intent intent)694   public void setManageBlockNumbersIntent(Intent intent) {
695     this.manageBlockNumbersIntent = intent;
696   }
697 
698   @Implementation(maxSdk = LOLLIPOP_MR1)
setSimCallManager(PhoneAccountHandle simCallManager)699   public void setSimCallManager(PhoneAccountHandle simCallManager) {
700     this.simCallManager = simCallManager;
701   }
702 
703   /**
704    * Creates a new {@link CallAudioState}. The real constructor of {@link CallAudioState} is hidden.
705    */
newCallAudioState( boolean muted, int route, int supportedRouteMask, BluetoothDevice activeBluetoothDevice, Collection<BluetoothDevice> supportedBluetoothDevices)706   public CallAudioState newCallAudioState(
707       boolean muted,
708       int route,
709       int supportedRouteMask,
710       BluetoothDevice activeBluetoothDevice,
711       Collection<BluetoothDevice> supportedBluetoothDevices) {
712     return new CallAudioState(
713         muted, route, supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
714   }
715 
716   @Implementation(minSdk = R)
717   @SystemApi
createLaunchEmergencyDialerIntent(String number)718   protected Intent createLaunchEmergencyDialerIntent(String number) {
719     // copy of logic from TelecomManager service
720     Context context = ReflectionHelpers.getField(realObject, "mContext");
721     // use reflection to get resource id since it can vary based on SDK version, and compiler will
722     // inline the value if used explicitly
723     int configEmergencyDialerPackageId =
724         ReflectionHelpers.getStaticField(
725             com.android.internal.R.string.class, "config_emergency_dialer_package");
726     String packageName = context.getString(configEmergencyDialerPackageId);
727     Intent intent = new Intent(Intent.ACTION_DIAL_EMERGENCY).setPackage(packageName);
728     ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
729     if (resolveInfo == null) {
730       // No matching activity from config, fallback to default platform implementation
731       intent.setPackage(null);
732     }
733     if (!TextUtils.isEmpty(number) && TextUtils.isDigitsOnly(number)) {
734       intent.setData(Uri.parse("tel:" + number));
735     }
736     return intent;
737   }
738 
739   @Implementation(minSdk = O)
isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle)740   protected boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
741     return this.isOutgoingCallPermitted;
742   }
743 
744   /**
745    * Details about a call request made via {@link TelecomManager#addNewIncomingCall} or {@link
746    * TelecomManager#addNewUnknownCall}.
747    *
748    * @deprecated Use {@link IncomingCallRecord} or {@link UnknownCallRecord} instead.
749    */
750   @Deprecated
751   public static class CallRecord {
752     public final PhoneAccountHandle phoneAccount;
753     public final Bundle extras;
754     protected boolean isRinging = true;
755 
756     /** @deprecated Use {@link extras} instead. */
757     @Deprecated public final Bundle bundle;
758 
CallRecord(PhoneAccountHandle phoneAccount, Bundle extras)759     public CallRecord(PhoneAccountHandle phoneAccount, Bundle extras) {
760       this.phoneAccount = phoneAccount;
761       this.extras = extras == null ? null : new Bundle(extras);
762 
763       // Keep the deprecated "bundle" name around for a while.
764       this.bundle = this.extras;
765     }
766   }
767 
768   /**
769    * When set to false methods requiring {@link android.Manifest.permission.READ_PHONE_STATE}
770    * permission will throw a {@link SecurityException}. By default it's set to true for backwards
771    * compatibility.
772    */
setReadPhoneStatePermission(boolean readPhoneStatePermission)773   public void setReadPhoneStatePermission(boolean readPhoneStatePermission) {
774     this.readPhoneStatePermission = readPhoneStatePermission;
775   }
776 
checkReadPhoneStatePermission()777   private void checkReadPhoneStatePermission() {
778     if (!readPhoneStatePermission) {
779       throw new SecurityException();
780     }
781   }
782 
783   /**
784    * When set to false methods requiring {@link android.Manifest.permission.CALL_PHONE} permission
785    * will throw a {@link SecurityException}. By default it's set to true for backwards
786    * compatibility.
787    */
setCallPhonePermission(boolean callPhonePermission)788   public void setCallPhonePermission(boolean callPhonePermission) {
789     this.callPhonePermission = callPhonePermission;
790   }
791 
checkCallPhonePermission()792   private void checkCallPhonePermission() {
793     if (!callPhonePermission) {
794       throw new SecurityException();
795     }
796   }
797 
798   /** Details about an incoming call request made via {@link TelecomManager#addNewIncomingCall}. */
799   public static class IncomingCallRecord extends CallRecord {
800     private boolean isHandled = false;
801 
IncomingCallRecord(PhoneAccountHandle phoneAccount, Bundle extras)802     public IncomingCallRecord(PhoneAccountHandle phoneAccount, Bundle extras) {
803       super(phoneAccount, extras);
804     }
805   }
806 
807   /** Details about an outgoing call request made via {@link TelecomManager#placeCall}. */
808   public static class OutgoingCallRecord {
809     public final PhoneAccountHandle phoneAccount;
810     public final Uri address;
811     public final Bundle extras;
812 
813     private boolean isHandled = false;
814 
OutgoingCallRecord(Uri address, Bundle extras)815     public OutgoingCallRecord(Uri address, Bundle extras) {
816       this.address = address;
817       if (extras != null) {
818         this.extras = new Bundle(extras);
819         this.phoneAccount = extras.getParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
820       } else {
821         this.extras = null;
822         this.phoneAccount = null;
823       }
824     }
825   }
826 
827   /** Details about an unknown call request made via {@link TelecomManager#addNewUnknownCall}. */
828   public static class UnknownCallRecord extends CallRecord {
UnknownCallRecord(PhoneAccountHandle phoneAccount, Bundle extras)829     public UnknownCallRecord(PhoneAccountHandle phoneAccount, Bundle extras) {
830       super(phoneAccount, extras);
831     }
832   }
833 }
834