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