All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowTelecomManager Maven / Gradle / Ivy

package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.R;
import static com.google.common.base.Verify.verifyNotNull;

import android.annotation.SystemApi;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.ConnectionService;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.text.TextUtils;
import android.util.ArrayMap;
import androidx.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.robolectric.android.controller.ServiceController;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.util.ReflectionHelpers;

@Implements(value = TelecomManager.class, minSdk = LOLLIPOP)
public class ShadowTelecomManager {

  /**
   * Mode describing how the shadow handles incoming ({@link TelecomManager#addNewIncomingCall}) and
   * outgoing ({@link TelecomManager#placeCall}) call requests.
   */
  public enum CallRequestMode {
    /** Automatically allows all call requests. */
    ALLOW_ALL,

    /** Automatically denies all call requests. */
    DENY_ALL,

    /**
     * Do not automatically allow or deny any call requests. Instead, call requests should be
     * allowed or denied manually by calling the following methods:
     *
     * 
    *
  • {@link #allowIncomingCall(IncomingCallRecord)} *
  • {@link #denyIncomingCall(IncomingCallRecord)} *
  • {@link #allowOutgoingCall(OutgoingCallRecord)} *
  • {@link #denyOutgoingCall(OutgoingCallRecord)} *
*/ MANUAL, } @RealObject private TelecomManager realObject; private final LinkedHashMap accounts = new LinkedHashMap<>(); private final LinkedHashMap voicemailNumbers = new LinkedHashMap<>(); private final List incomingCalls = new ArrayList<>(); private final List outgoingCalls = new ArrayList<>(); private final List unknownCalls = new ArrayList<>(); private final Map defaultOutgoingPhoneAccounts = new ArrayMap<>(); private Intent manageBlockNumbersIntent; private CallRequestMode callRequestMode = CallRequestMode.MANUAL; private PhoneAccountHandle simCallManager; private String defaultDialerPackageName; private String systemDefaultDialerPackageName; private boolean isInCall; private boolean ttySupported; private PhoneAccountHandle userSelectedOutgoingPhoneAccount; public CallRequestMode getCallRequestMode() { return callRequestMode; } public void setCallRequestMode(CallRequestMode callRequestMode) { this.callRequestMode = callRequestMode; } /** * Set default outgoing phone account to be returned from {@link * #getDefaultOutgoingPhoneAccount(String)} for corresponding {@code uriScheme}. */ public void setDefaultOutgoingPhoneAccount(String uriScheme, PhoneAccountHandle handle) { defaultOutgoingPhoneAccounts.put(uriScheme, handle); } /** Remove default outgoing phone account for corresponding {@code uriScheme}. */ public void removeDefaultOutgoingPhoneAccount(String uriScheme) { defaultOutgoingPhoneAccounts.remove(uriScheme); } /** * Returns default outgoing phone account set through {@link * #setDefaultOutgoingPhoneAccount(String, PhoneAccountHandle)} for corresponding {@code * uriScheme}. */ @Implementation protected PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) { return defaultOutgoingPhoneAccounts.get(uriScheme); } @Implementation @HiddenApi public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() { return userSelectedOutgoingPhoneAccount; } @Implementation @HiddenApi public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { userSelectedOutgoingPhoneAccount = accountHandle; } @Implementation protected PhoneAccountHandle getSimCallManager() { return simCallManager; } @Implementation(minSdk = M) @HiddenApi public PhoneAccountHandle getSimCallManager(int userId) { return null; } @Implementation @HiddenApi public PhoneAccountHandle getConnectionManager() { return this.getSimCallManager(); } @Implementation @HiddenApi public List getPhoneAccountsSupportingScheme(String uriScheme) { List result = new ArrayList<>(); for (PhoneAccountHandle handle : accounts.keySet()) { PhoneAccount phoneAccount = accounts.get(handle); if (phoneAccount.getSupportedUriSchemes().contains(uriScheme)) { result.add(handle); } } return result; } @Implementation(minSdk = M) protected List getCallCapablePhoneAccounts() { return this.getCallCapablePhoneAccounts(false); } @Implementation(minSdk = M) @HiddenApi public List getCallCapablePhoneAccounts(boolean includeDisabledAccounts) { List result = new ArrayList<>(); for (PhoneAccountHandle handle : accounts.keySet()) { PhoneAccount phoneAccount = accounts.get(handle); if (!phoneAccount.isEnabled() && !includeDisabledAccounts) { continue; } result.add(handle); } return result; } @Implementation(minSdk = O) public List getSelfManagedPhoneAccounts() { List result = new ArrayList<>(); for (PhoneAccountHandle handle : accounts.keySet()) { PhoneAccount phoneAccount = accounts.get(handle); if ((phoneAccount.getCapabilities() & PhoneAccount.CAPABILITY_SELF_MANAGED) == PhoneAccount.CAPABILITY_SELF_MANAGED) { result.add(handle); } } return result; } @Implementation @HiddenApi public List getPhoneAccountsForPackage() { Context context = ReflectionHelpers.getField(realObject, "mContext"); List results = new ArrayList<>(); for (PhoneAccountHandle handle : accounts.keySet()) { if (handle.getComponentName().getPackageName().equals(context.getPackageName())) { results.add(handle); } } return results; } @Implementation protected PhoneAccount getPhoneAccount(PhoneAccountHandle account) { return accounts.get(account); } @Implementation @HiddenApi public int getAllPhoneAccountsCount() { return accounts.size(); } @Implementation @HiddenApi public List getAllPhoneAccounts() { return ImmutableList.copyOf(accounts.values()); } @Implementation @HiddenApi public List getAllPhoneAccountHandles() { return ImmutableList.copyOf(accounts.keySet()); } @Implementation protected void registerPhoneAccount(PhoneAccount account) { accounts.put(account.getAccountHandle(), account); } @Implementation protected void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { accounts.remove(accountHandle); } /** @deprecated */ @Deprecated @Implementation @HiddenApi public void clearAccounts() { accounts.clear(); } @Implementation(minSdk = LOLLIPOP_MR1) @HiddenApi public void clearAccountsForPackage(String packageName) { Set phoneAccountHandlesInPackage = new HashSet<>(); for (PhoneAccountHandle handle : accounts.keySet()) { if (handle.getComponentName().getPackageName().equals(packageName)) { phoneAccountHandlesInPackage.add(handle); } } for (PhoneAccountHandle handle : phoneAccountHandlesInPackage) { accounts.remove(handle); } } /** @deprecated */ @Deprecated @Implementation @HiddenApi public ComponentName getDefaultPhoneApp() { return null; } @Implementation(minSdk = M) protected String getDefaultDialerPackage() { return defaultDialerPackageName; } /** @deprecated API deprecated since Q, for testing, use setDefaultDialerPackage instead */ @Deprecated @Implementation(minSdk = M) @HiddenApi public boolean setDefaultDialer(String packageName) { this.defaultDialerPackageName = packageName; return true; } /** Set returned value of {@link #getDefaultDialerPackage()}. */ public void setDefaultDialerPackage(String packageName) { this.defaultDialerPackageName = packageName; } @Implementation(minSdk = M) @HiddenApi // API goes public in Q protected String getSystemDialerPackage() { return systemDefaultDialerPackageName; } /** Set returned value of {@link #getSystemDialerPackage()}. */ public void setSystemDialerPackage(String packageName) { this.systemDefaultDialerPackageName = packageName; } public void setVoicemailNumber(PhoneAccountHandle accountHandle, String number) { voicemailNumbers.put(accountHandle, number); } @Implementation(minSdk = M) protected boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) { return TextUtils.equals(number, voicemailNumbers.get(accountHandle)); } @Implementation(minSdk = M) protected String getVoiceMailNumber(PhoneAccountHandle accountHandle) { return voicemailNumbers.get(accountHandle); } @Implementation(minSdk = LOLLIPOP_MR1) protected String getLine1Number(PhoneAccountHandle accountHandle) { return null; } /** Sets the return value for {@link TelecomManager#isInCall}. */ public void setIsInCall(boolean isInCall) { this.isInCall = isInCall; } /** * Overrides behavior of {@link TelecomManager#isInCall} to return pre-set result. * * @return Value set by calling {@link ShadowTelecomManager#setIsInCall}. If setIsInCall has not * previously been called, will return false. */ @Implementation protected boolean isInCall() { return isInCall; } @Implementation @HiddenApi public int getCallState() { return 0; } @Implementation @HiddenApi public boolean isRinging() { for (IncomingCallRecord callRecord : incomingCalls) { if (callRecord.isRinging) { return true; } } for (UnknownCallRecord callRecord : unknownCalls) { if (callRecord.isRinging) { return true; } } return false; } @Implementation @HiddenApi public boolean endCall() { return false; } @Implementation protected void acceptRingingCall() {} @Implementation protected void silenceRinger() { for (IncomingCallRecord callRecord : incomingCalls) { callRecord.isRinging = false; } for (UnknownCallRecord callRecord : unknownCalls) { callRecord.isRinging = false; } } @Implementation protected boolean isTtySupported() { return ttySupported; } /** Sets the value to be returned by {@link #isTtySupported()}. */ public void setTtySupported(boolean isSupported) { ttySupported = isSupported; } @Implementation @HiddenApi public int getCurrentTtyMode() { return 0; } @Implementation protected void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) { IncomingCallRecord call = new IncomingCallRecord(phoneAccount, extras); incomingCalls.add(call); switch (callRequestMode) { case ALLOW_ALL: allowIncomingCall(call); break; case DENY_ALL: denyIncomingCall(call); break; default: // Do nothing. } } public List getAllIncomingCalls() { return ImmutableList.copyOf(incomingCalls); } public IncomingCallRecord getLastIncomingCall() { return Iterables.getLast(incomingCalls); } public IncomingCallRecord getOnlyIncomingCall() { return Iterables.getOnlyElement(incomingCalls); } /** * Allows an {@link IncomingCallRecord} created via {@link TelecomManager#addNewIncomingCall}. * *

Specifically, this method sets up the relevant {@link ConnectionService} and returns the * result of {@link ConnectionService#onCreateIncomingConnection}. */ @TargetApi(M) @Nullable public Connection allowIncomingCall(IncomingCallRecord call) { if (call.isHandled) { throw new IllegalStateException("Call has already been allowed or denied."); } call.isHandled = true; PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); ConnectionRequest request = buildConnectionRequestForIncomingCall(call); ConnectionService service = setupConnectionService(phoneAccount); return service.onCreateIncomingConnection(phoneAccount, request); } /** * Denies an {@link IncomingCallRecord} created via {@link TelecomManager#addNewIncomingCall}. * *

Specifically, this method sets up the relevant {@link ConnectionService} and calls {@link * ConnectionService#onCreateIncomingConnectionFailed}. */ @TargetApi(O) public void denyIncomingCall(IncomingCallRecord call) { if (call.isHandled) { throw new IllegalStateException("Call has already been allowed or denied."); } call.isHandled = true; PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); ConnectionRequest request = buildConnectionRequestForIncomingCall(call); ConnectionService service = setupConnectionService(phoneAccount); service.onCreateIncomingConnectionFailed(phoneAccount, request); } private static ConnectionRequest buildConnectionRequestForIncomingCall(IncomingCallRecord call) { PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); Bundle extras = verifyNotNull(call.extras); Uri address = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS); int videoState = extras.getInt( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY); return new ConnectionRequest(phoneAccount, address, new Bundle(extras), videoState); } @Implementation(minSdk = M) protected void placeCall(Uri address, Bundle extras) { OutgoingCallRecord call = new OutgoingCallRecord(address, extras); outgoingCalls.add(call); switch (callRequestMode) { case ALLOW_ALL: allowOutgoingCall(call); break; case DENY_ALL: denyOutgoingCall(call); break; default: // Do nothing. } } public List getAllOutgoingCalls() { return ImmutableList.copyOf(outgoingCalls); } public OutgoingCallRecord getLastOutgoingCall() { return Iterables.getLast(outgoingCalls); } public OutgoingCallRecord getOnlyOutgoingCall() { return Iterables.getOnlyElement(outgoingCalls); } /** * Allows an {@link OutgoingCallRecord} created via {@link TelecomManager#placeCall}. * *

Specifically, this method sets up the relevant {@link ConnectionService} and returns the * result of {@link ConnectionService#onCreateOutgoingConnection}. */ @TargetApi(M) @Nullable public Connection allowOutgoingCall(OutgoingCallRecord call) { if (call.isHandled) { throw new IllegalStateException("Call has already been allowed or denied."); } call.isHandled = true; PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); ConnectionRequest request = buildConnectionRequestForOutgoingCall(call); ConnectionService service = setupConnectionService(phoneAccount); return service.onCreateOutgoingConnection(phoneAccount, request); } /** * Denies an {@link OutgoingCallRecord} created via {@link TelecomManager#placeCall}. * *

Specifically, this method sets up the relevant {@link ConnectionService} and calls {@link * ConnectionService#onCreateOutgoingConnectionFailed}. */ @TargetApi(O) public void denyOutgoingCall(OutgoingCallRecord call) { if (call.isHandled) { throw new IllegalStateException("Call has already been allowed or denied."); } call.isHandled = true; PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); ConnectionRequest request = buildConnectionRequestForOutgoingCall(call); ConnectionService service = setupConnectionService(phoneAccount); service.onCreateOutgoingConnectionFailed(phoneAccount, request); } private static ConnectionRequest buildConnectionRequestForOutgoingCall(OutgoingCallRecord call) { PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); Uri address = verifyNotNull(call.address); Bundle extras = verifyNotNull(call.extras); Bundle outgoingCallExtras = extras.getBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); int videoState = extras.getInt( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY); return new ConnectionRequest( phoneAccount, address, outgoingCallExtras == null ? null : new Bundle(outgoingCallExtras), videoState); } @Implementation @HiddenApi public void addNewUnknownCall(PhoneAccountHandle phoneAccount, Bundle extras) { unknownCalls.add(new UnknownCallRecord(phoneAccount, extras)); } public List getAllUnknownCalls() { return ImmutableList.copyOf(unknownCalls); } public UnknownCallRecord getLastUnknownCall() { return Iterables.getLast(unknownCalls); } public UnknownCallRecord getOnlyUnknownCall() { return Iterables.getOnlyElement(unknownCalls); } private static ConnectionService setupConnectionService(PhoneAccountHandle phoneAccount) { ComponentName service = phoneAccount.getComponentName(); Class clazz; try { clazz = Class.forName(service.getClassName()).asSubclass(ConnectionService.class); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } return verifyNotNull( ServiceController.of(ReflectionHelpers.callConstructor(clazz), null).create().get()); } @Implementation protected boolean handleMmi(String dialString) { return false; } @Implementation(minSdk = M) protected boolean handleMmi(String dialString, PhoneAccountHandle accountHandle) { return false; } @Implementation(minSdk = LOLLIPOP_MR1) protected Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) { return Uri.parse("content://icc/adn"); } @Implementation protected void cancelMissedCallsNotification() {} @Implementation protected void showInCallScreen(boolean showDialpad) {} @Implementation(minSdk = M) @HiddenApi public void enablePhoneAccount(PhoneAccountHandle handle, boolean isEnabled) { } /** * Returns the intent set by {@link ShadowTelecomManager#setManageBlockNumbersIntent(Intent)} ()} */ @Implementation(minSdk = N) protected Intent createManageBlockedNumbersIntent() { return this.manageBlockNumbersIntent; } /** * Sets the BlockNumbersIntent to be returned by {@link * ShadowTelecomManager#createManageBlockedNumbersIntent()} */ public void setManageBlockNumbersIntent(Intent intent) { this.manageBlockNumbersIntent = intent; } @Implementation(maxSdk = LOLLIPOP_MR1) public void setSimCallManager(PhoneAccountHandle simCallManager) { this.simCallManager = simCallManager; } /** * Creates a new {@link CallAudioState}. The real constructor of {@link CallAudioState} is hidden. */ public CallAudioState newCallAudioState( boolean muted, int route, int supportedRouteMask, BluetoothDevice activeBluetoothDevice, Collection supportedBluetoothDevices) { return new CallAudioState( muted, route, supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices); } @Implementation(minSdk = R) @SystemApi protected Intent createLaunchEmergencyDialerIntent(String number) { // copy of logic from TelecomManager service Context context = ReflectionHelpers.getField(realObject, "mContext"); // use reflection to get resource id since it can vary based on SDK version, and compiler will // inline the value if used explicitly int configEmergencyDialerPackageId = ReflectionHelpers.getStaticField( com.android.internal.R.string.class, "config_emergency_dialer_package"); String packageName = context.getString(configEmergencyDialerPackageId); Intent intent = new Intent(Intent.ACTION_DIAL_EMERGENCY).setPackage(packageName); ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0); if (resolveInfo == null) { // No matching activity from config, fallback to default platform implementation intent.setPackage(null); } if (!TextUtils.isEmpty(number) && TextUtils.isDigitsOnly(number)) { intent.setData(Uri.parse("tel:" + number)); } return intent; } /** * Details about a call request made via {@link TelecomManager#addNewIncomingCall} or {@link * TelecomManager#addNewUnknownCall}. * * @deprecated Use {@link IncomingCallRecord} or {@link UnknownCallRecord} instead. */ @Deprecated public static class CallRecord { public final PhoneAccountHandle phoneAccount; public final Bundle extras; protected boolean isRinging = true; /** @deprecated Use {@link extras} instead. */ @Deprecated public final Bundle bundle; public CallRecord(PhoneAccountHandle phoneAccount, Bundle extras) { this.phoneAccount = phoneAccount; this.extras = extras == null ? null : new Bundle(extras); // Keep the deprecated "bundle" name around for a while. this.bundle = this.extras; } } /** Details about an incoming call request made via {@link TelecomManager#addNewIncomingCall}. */ public static class IncomingCallRecord extends CallRecord { private boolean isHandled = false; public IncomingCallRecord(PhoneAccountHandle phoneAccount, Bundle extras) { super(phoneAccount, extras); } } /** Details about an outgoing call request made via {@link TelecomManager#placeCall}. */ public static class OutgoingCallRecord { public final PhoneAccountHandle phoneAccount; public final Uri address; public final Bundle extras; private boolean isHandled = false; public OutgoingCallRecord(Uri address, Bundle extras) { this.address = address; if (extras != null) { this.extras = new Bundle(extras); this.phoneAccount = extras.getParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); } else { this.extras = null; this.phoneAccount = null; } } } /** Details about an unknown call request made via {@link TelecomManager#addNewUnknownCall}. */ public static class UnknownCallRecord extends CallRecord { public UnknownCallRecord(PhoneAccountHandle phoneAccount, Bundle extras) { super(phoneAccount, extras); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy