1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.dialer.simulator.impl; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.net.Uri; 22 import android.os.Bundle; 23 import android.support.annotation.IntDef; 24 import android.support.annotation.NonNull; 25 import android.telecom.Connection; 26 import android.telecom.ConnectionRequest; 27 import android.telecom.PhoneAccount; 28 import android.telecom.PhoneAccountHandle; 29 import android.telecom.TelecomManager; 30 import android.telephony.TelephonyManager; 31 import com.android.dialer.common.Assert; 32 import com.android.dialer.common.LogUtil; 33 import com.android.dialer.strictmode.StrictModeUtils; 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 import java.util.Arrays; 37 import java.util.List; 38 import java.util.Random; 39 40 /** 41 * Utility to use the simulator connection service to add phone calls. To ensure that the added 42 * calls are routed through the simulator we register ourselves as a SIM call manager using 43 * CAPABILITY_CONNECTION_MANAGER. This ensures that all calls on the device must first go through 44 * our connection service. 45 * 46 * <p>For video calls this will only work if the underlying telephony phone account also supports 47 * video. To ensure that video always works we use a separate video account. The user must manually 48 * enable this account in call settings for video calls to work. 49 */ 50 public class SimulatorSimCallManager { 51 52 public static final int CALL_TYPE_VOICE = 1; 53 public static final int CALL_TYPE_VIDEO = 2; 54 public static final int CALL_TYPE_RTT = 3; 55 56 /** Call type of a simulator call. */ 57 @Retention(RetentionPolicy.SOURCE) 58 @IntDef({CALL_TYPE_VOICE, CALL_TYPE_VIDEO, CALL_TYPE_RTT}) 59 public @interface CallType {} 60 61 private static final String SIM_CALL_MANAGER_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID"; 62 private static final String VIDEO_PROVIDER_ACCOUNT_ID = "SIMULATOR_VIDEO_ACCOUNT_ID"; 63 private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection"; 64 private static final String EXTRA_CONNECTION_TAG = "connection_tag"; 65 private static final String EXTRA_CONNECTION_CALL_TYPE = "connection_call_type"; 66 register(@onNull Context context)67 public static void register(@NonNull Context context) { 68 LogUtil.enterBlock("SimulatorSimCallManager.register"); 69 Assert.isNotNull(context); 70 StrictModeUtils.bypass( 71 () -> { 72 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 73 telecomManager.registerPhoneAccount(buildSimCallManagerAccount(context)); 74 telecomManager.registerPhoneAccount(buildVideoProviderAccount(context)); 75 }); 76 } 77 unregister(@onNull Context context)78 public static void unregister(@NonNull Context context) { 79 LogUtil.enterBlock("SimulatorSimCallManager.unregister"); 80 Assert.isNotNull(context); 81 StrictModeUtils.bypass( 82 () -> { 83 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 84 telecomManager.unregisterPhoneAccount(getSimCallManagerHandle(context)); 85 telecomManager.unregisterPhoneAccount(getVideoProviderHandle(context)); 86 }); 87 } 88 89 @NonNull addNewOutgoingCall( @onNull Context context, @NonNull String phoneNumber, @CallType int callType)90 public static String addNewOutgoingCall( 91 @NonNull Context context, @NonNull String phoneNumber, @CallType int callType) { 92 return addNewOutgoingCall(context, phoneNumber, callType, new Bundle()); 93 } 94 95 @NonNull addNewOutgoingCall( @onNull Context context, @NonNull String phoneNumber, @CallType int callType, @NonNull Bundle extras)96 public static String addNewOutgoingCall( 97 @NonNull Context context, 98 @NonNull String phoneNumber, 99 @CallType int callType, 100 @NonNull Bundle extras) { 101 LogUtil.enterBlock("SimulatorSimCallManager.addNewOutgoingCall"); 102 Assert.isNotNull(context); 103 Assert.isNotNull(extras); 104 Assert.isNotNull(phoneNumber); 105 Assert.isNotNull(extras); 106 107 register(context); 108 109 extras = new Bundle(extras); 110 extras.putAll(createSimulatorConnectionExtras(callType)); 111 112 Bundle outgoingCallExtras = new Bundle(); 113 outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); 114 outgoingCallExtras.putParcelable( 115 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, 116 callType == CALL_TYPE_VIDEO 117 ? getVideoProviderHandle(context) 118 : getSimCallManagerHandle(context)); 119 if (callType == CALL_TYPE_RTT) { 120 outgoingCallExtras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true); 121 } 122 123 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 124 try { 125 telecomManager.placeCall( 126 Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null), outgoingCallExtras); 127 } catch (SecurityException e) { 128 throw Assert.createIllegalStateFailException("Unable to place call: " + e); 129 } 130 return extras.getString(EXTRA_CONNECTION_TAG); 131 } 132 133 @NonNull addNewIncomingCall( @onNull Context context, @NonNull String callerId, @CallType int callType)134 public static String addNewIncomingCall( 135 @NonNull Context context, @NonNull String callerId, @CallType int callType) { 136 return addNewIncomingCall(context, callerId, callType, new Bundle()); 137 } 138 139 @NonNull addNewIncomingCall( @onNull Context context, @NonNull String callerId, @CallType int callType, @NonNull Bundle extras)140 public static String addNewIncomingCall( 141 @NonNull Context context, 142 @NonNull String callerId, 143 @CallType int callType, 144 @NonNull Bundle extras) { 145 LogUtil.enterBlock("SimulatorSimCallManager.addNewIncomingCall"); 146 Assert.isNotNull(context); 147 Assert.isNotNull(callerId); 148 Assert.isNotNull(extras); 149 150 register(context); 151 152 extras = new Bundle(extras); 153 extras.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, callerId); 154 extras.putAll(createSimulatorConnectionExtras(callType)); 155 156 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 157 telecomManager.addNewIncomingCall( 158 callType == CALL_TYPE_VIDEO 159 ? getVideoProviderHandle(context) 160 : getSystemPhoneAccountHandle(context), 161 extras); 162 return extras.getString(EXTRA_CONNECTION_TAG); 163 } 164 165 @NonNull buildSimCallManagerAccount(Context context)166 private static PhoneAccount buildSimCallManagerAccount(Context context) { 167 return new PhoneAccount.Builder(getSimCallManagerHandle(context), "Simulator SIM call manager") 168 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER | PhoneAccount.CAPABILITY_RTT) 169 .setShortDescription("Simulator SIM call manager") 170 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL)) 171 .build(); 172 } 173 174 @NonNull buildVideoProviderAccount(Context context)175 private static PhoneAccount buildVideoProviderAccount(Context context) { 176 return new PhoneAccount.Builder(getVideoProviderHandle(context), "Simulator video provider") 177 .setCapabilities( 178 PhoneAccount.CAPABILITY_CALL_PROVIDER 179 | PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING 180 | PhoneAccount.CAPABILITY_VIDEO_CALLING) 181 .setShortDescription("Simulator video provider") 182 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL)) 183 .build(); 184 } 185 186 @NonNull getSimCallManagerHandle(@onNull Context context)187 public static PhoneAccountHandle getSimCallManagerHandle(@NonNull Context context) { 188 return new PhoneAccountHandle( 189 new ComponentName(context, SimulatorConnectionService.class), SIM_CALL_MANAGER_ACCOUNT_ID); 190 } 191 192 @NonNull getVideoProviderHandle(@onNull Context context)193 static PhoneAccountHandle getVideoProviderHandle(@NonNull Context context) { 194 return new PhoneAccountHandle( 195 new ComponentName(context, SimulatorConnectionService.class), VIDEO_PROVIDER_ACCOUNT_ID); 196 } 197 198 @NonNull getSystemPhoneAccountHandle(@onNull Context context)199 public static PhoneAccountHandle getSystemPhoneAccountHandle(@NonNull Context context) { 200 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 201 List<PhoneAccountHandle> handles; 202 try { 203 handles = telecomManager.getCallCapablePhoneAccounts(); 204 } catch (SecurityException e) { 205 throw Assert.createIllegalStateFailException("Unable to get phone accounts: " + e); 206 } 207 for (PhoneAccountHandle handle : handles) { 208 PhoneAccount account = telecomManager.getPhoneAccount(handle); 209 if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 210 return handle; 211 } 212 } 213 throw Assert.createIllegalStateFailException("no SIM phone account available"); 214 } 215 isSimulatorConnectionRequest(@onNull ConnectionRequest request)216 public static boolean isSimulatorConnectionRequest(@NonNull ConnectionRequest request) { 217 return request.getExtras() != null 218 && request.getExtras().getBoolean(EXTRA_IS_SIMULATOR_CONNECTION); 219 } 220 221 @NonNull getConnectionTag(@onNull Connection connection)222 public static String getConnectionTag(@NonNull Connection connection) { 223 String connectionTag = connection.getExtras().getString(EXTRA_CONNECTION_TAG); 224 return Assert.isNotNull(connectionTag); 225 } 226 227 @NonNull findConnectionByTag(@onNull String connectionTag)228 public static SimulatorConnection findConnectionByTag(@NonNull String connectionTag) { 229 Assert.isNotNull(connectionTag); 230 for (Connection connection : SimulatorConnectionService.getInstance().getAllConnections()) { 231 if (connection.getExtras().getBoolean(connectionTag)) { 232 return (SimulatorConnection) connection; 233 } 234 } 235 throw Assert.createIllegalStateFailException(); 236 } 237 238 @NonNull createUniqueConnectionTag()239 private static String createUniqueConnectionTag() { 240 int callId = new Random().nextInt(); 241 return String.format("simulator_phone_call_%x", Math.abs(callId)); 242 } 243 244 @NonNull createSimulatorConnectionExtras(@allType int callType)245 static Bundle createSimulatorConnectionExtras(@CallType int callType) { 246 Bundle extras = new Bundle(); 247 extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true); 248 String connectionTag = createUniqueConnectionTag(); 249 extras.putString(EXTRA_CONNECTION_TAG, connectionTag); 250 extras.putBoolean(connectionTag, true); 251 extras.putInt(EXTRA_CONNECTION_CALL_TYPE, callType); 252 if (callType == CALL_TYPE_RTT) { 253 extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true); 254 } 255 return extras; 256 } 257 SimulatorSimCallManager()258 private SimulatorSimCallManager() {} 259 } 260