• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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