blob: 905304e3ce4ceaec75f0a9dff0b9c5de53070a90 [file] [log] [blame]
Ihab Awad542e0ea2014-05-16 10:22:16 -07001/*
2 * Copyright (C) 2014 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
17package android.telecomm;
18
Sailesh Nepal2a46b902014-07-04 17:21:07 -070019import android.app.Service;
20import android.content.Intent;
Santos Cordon52d8a152014-06-17 19:08:45 -070021import android.content.ComponentName;
Ihab Awad542e0ea2014-05-16 10:22:16 -070022import android.net.Uri;
23import android.os.Bundle;
Santos Cordon52d8a152014-06-17 19:08:45 -070024import android.os.Handler;
25import android.os.IBinder;
26import android.os.Looper;
Sailesh Nepal2a46b902014-07-04 17:21:07 -070027import android.os.Message;
Ihab Awad542e0ea2014-05-16 10:22:16 -070028
Sailesh Nepal2a46b902014-07-04 17:21:07 -070029import com.android.internal.os.SomeArgs;
30import com.android.internal.telecomm.IConnectionService;
31import com.android.internal.telecomm.IConnectionServiceAdapter;
Santos Cordon52d8a152014-06-17 19:08:45 -070032import com.android.internal.telecomm.RemoteServiceCallback;
33
Santos Cordonb6939982014-06-04 20:20:58 -070034import java.util.Collection;
Ihab Awad542e0ea2014-05-16 10:22:16 -070035import java.util.HashMap;
Santos Cordon52d8a152014-06-17 19:08:45 -070036import java.util.List;
Ihab Awad542e0ea2014-05-16 10:22:16 -070037import java.util.Map;
38
39/**
Sailesh Nepal2a46b902014-07-04 17:21:07 -070040 * A {@link android.app.Service} that provides telephone connections to processes running on an
41 * Android device.
Ihab Awad542e0ea2014-05-16 10:22:16 -070042 */
Sailesh Nepal2a46b902014-07-04 17:21:07 -070043public abstract class ConnectionService extends Service {
Ihab Awad542e0ea2014-05-16 10:22:16 -070044 // Flag controlling whether PII is emitted into the logs
Ihab Awad60ac30b2014-05-20 22:32:12 -070045 private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
Ihab Awad542e0ea2014-05-16 10:22:16 -070046 private static final Connection NULL_CONNECTION = new Connection() {};
47
Sailesh Nepal2a46b902014-07-04 17:21:07 -070048 private static final int MSG_ADD_CALL_SERVICE_ADAPTER = 1;
49 private static final int MSG_CALL = 2;
50 private static final int MSG_ABORT = 3;
51 private static final int MSG_CREATE_INCOMING_CALL = 4;
52 private static final int MSG_ANSWER = 5;
53 private static final int MSG_REJECT = 6;
54 private static final int MSG_DISCONNECT = 7;
55 private static final int MSG_HOLD = 8;
56 private static final int MSG_UNHOLD = 9;
57 private static final int MSG_ON_AUDIO_STATE_CHANGED = 10;
58 private static final int MSG_PLAY_DTMF_TONE = 11;
59 private static final int MSG_STOP_DTMF_TONE = 12;
60 private static final int MSG_CONFERENCE = 13;
61 private static final int MSG_SPLIT_FROM_CONFERENCE = 14;
62 private static final int MSG_ON_POST_DIAL_CONTINUE = 15;
63 private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 16;
64
Ihab Awad542e0ea2014-05-16 10:22:16 -070065 private final Map<String, Connection> mConnectionById = new HashMap<>();
66 private final Map<Connection, String> mIdByConnection = new HashMap<>();
Santos Cordon52d8a152014-06-17 19:08:45 -070067 private final RemoteConnectionManager mRemoteConnectionManager = new RemoteConnectionManager();
Santos Cordon52d8a152014-06-17 19:08:45 -070068
Ihab Awad9c3f1882014-06-30 21:17:13 -070069 private SimpleResponse<Uri, List<PhoneAccount>> mAccountLookupResponse;
70 private Uri mAccountLookupHandle;
71 private boolean mAreAccountsInitialized = false;
Sailesh Nepal2a46b902014-07-04 17:21:07 -070072 private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
Ihab Awad542e0ea2014-05-16 10:22:16 -070073
Sailesh Nepal506e3862014-06-25 13:35:14 -070074 /**
75 * A callback for providing the resuilt of creating a connection.
76 */
77 public interface OutgoingCallResponse<CONNECTION> {
78 /**
79 * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
80 *
81 * @param request The original request.
82 * @param connection The connection.
83 */
84 void onSuccess(ConnectionRequest request, CONNECTION connection);
85
86 /**
87 * Tells Telecomm that an attempt to place the specified outgoing call failed.
88 *
89 * @param request The original request.
90 * @param code An integer code indicating the reason for failure.
91 * @param msg A message explaining the reason for failure.
92 */
93 void onFailure(ConnectionRequest request, int code, String msg);
94
95 /**
96 * Tells Telecomm to cancel the call.
97 *
98 * @param request The original request.
99 */
100 void onCancel(ConnectionRequest request);
101 }
102
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700103 private final IBinder mBinder = new IConnectionService.Stub() {
104 @Override
105 public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
106 mHandler.obtainMessage(MSG_ADD_CALL_SERVICE_ADAPTER, adapter).sendToTarget();
107 }
108
109 @Override
110 public void call(ConnectionRequest request) {
111 mHandler.obtainMessage(MSG_CALL, request).sendToTarget();
112 }
113
114 @Override
115 public void abort(String callId) {
116 mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
117 }
118
119 @Override
120 public void createIncomingCall(ConnectionRequest request) {
121 mHandler.obtainMessage(MSG_CREATE_INCOMING_CALL, request).sendToTarget();
122 }
123
124 @Override
125 public void answer(String callId) {
126 mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
127 }
128
129 @Override
130 public void reject(String callId) {
131 mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
132 }
133
134 @Override
135 public void disconnect(String callId) {
136 mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
137 }
138
139 @Override
140 public void hold(String callId) {
141 mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
142 }
143
144 @Override
145 public void unhold(String callId) {
146 mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
147 }
148
149 @Override
150 public void onAudioStateChanged(String callId, CallAudioState audioState) {
151 SomeArgs args = SomeArgs.obtain();
152 args.arg1 = callId;
153 args.arg2 = audioState;
154 mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget();
155 }
156
157 @Override
158 public void playDtmfTone(String callId, char digit) {
159 mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
160 }
161
162 @Override
163 public void stopDtmfTone(String callId) {
164 mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
165 }
166
167 @Override
168 public void conference(String conferenceCallId, String callId) {
169 SomeArgs args = SomeArgs.obtain();
170 args.arg1 = conferenceCallId;
171 args.arg2 = callId;
172 mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
173 }
174
175 @Override
176 public void splitFromConference(String callId) {
177 mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
178 }
179
180 @Override
181 public void onPostDialContinue(String callId, boolean proceed) {
182 SomeArgs args = SomeArgs.obtain();
183 args.arg1 = callId;
184 args.argi1 = proceed ? 1 : 0;
185 mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
186 }
187
188 @Override
189 public void onPhoneAccountClicked(String callId) {
190 mHandler.obtainMessage(MSG_ON_PHONE_ACCOUNT_CLICKED, callId).sendToTarget();
191 }
192 };
193
194 private final Handler mHandler = new Handler(Looper.getMainLooper()) {
195 @Override
196 public void handleMessage(Message msg) {
197 switch (msg.what) {
198 case MSG_ADD_CALL_SERVICE_ADAPTER:
199 mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
200 onAdapterAttached();
201 break;
202 case MSG_CALL:
203 call((ConnectionRequest) msg.obj);
204 break;
205 case MSG_ABORT:
206 abort((String) msg.obj);
207 break;
208 case MSG_CREATE_INCOMING_CALL:
209 createIncomingCall((ConnectionRequest) msg.obj);
210 break;
211 case MSG_ANSWER:
212 answer((String) msg.obj);
213 break;
214 case MSG_REJECT:
215 reject((String) msg.obj);
216 break;
217 case MSG_DISCONNECT:
218 disconnect((String) msg.obj);
219 break;
220 case MSG_HOLD:
221 hold((String) msg.obj);
222 break;
223 case MSG_UNHOLD:
224 unhold((String) msg.obj);
225 break;
226 case MSG_ON_AUDIO_STATE_CHANGED: {
227 SomeArgs args = (SomeArgs) msg.obj;
228 try {
229 String callId = (String) args.arg1;
230 CallAudioState audioState = (CallAudioState) args.arg2;
231 onAudioStateChanged(callId, audioState);
232 } finally {
233 args.recycle();
234 }
235 break;
236 }
237 case MSG_PLAY_DTMF_TONE:
238 playDtmfTone((String) msg.obj, (char) msg.arg1);
239 break;
240 case MSG_STOP_DTMF_TONE:
241 stopDtmfTone((String) msg.obj);
242 break;
243 case MSG_CONFERENCE: {
244 SomeArgs args = (SomeArgs) msg.obj;
245 try {
246 String conferenceCallId = (String) args.arg1;
247 String callId = (String) args.arg2;
248 conference(conferenceCallId, callId);
249 } finally {
250 args.recycle();
251 }
252 break;
253 }
254 case MSG_SPLIT_FROM_CONFERENCE:
255 splitFromConference((String) msg.obj);
256 break;
257 case MSG_ON_POST_DIAL_CONTINUE: {
258 SomeArgs args = (SomeArgs) msg.obj;
259 try {
260 String callId = (String) args.arg1;
261 boolean proceed = (args.argi1 == 1);
262 onPostDialContinue(callId, proceed);
263 } finally {
264 args.recycle();
265 }
266 break;
267 }
268 case MSG_ON_PHONE_ACCOUNT_CLICKED:
269 onPhoneAccountClicked((String) msg.obj);
270 break;
271 default:
272 break;
273 }
274 }
275 };
276
Ihab Awad542e0ea2014-05-16 10:22:16 -0700277 private final Connection.Listener mConnectionListener = new Connection.Listener() {
278 @Override
279 public void onStateChanged(Connection c, int state) {
280 String id = mIdByConnection.get(c);
Ihab Awad42b30e12014-05-22 09:49:34 -0700281 Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
Ihab Awad542e0ea2014-05-16 10:22:16 -0700282 switch (state) {
283 case Connection.State.ACTIVE:
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700284 mAdapter.setActive(id);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700285 break;
286 case Connection.State.DIALING:
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700287 mAdapter.setDialing(id);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700288 break;
289 case Connection.State.DISCONNECTED:
290 // Handled in onDisconnected()
291 break;
292 case Connection.State.HOLDING:
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700293 mAdapter.setOnHold(id);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700294 break;
295 case Connection.State.NEW:
296 // Nothing to tell Telecomm
297 break;
298 case Connection.State.RINGING:
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700299 mAdapter.setRinging(id);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700300 break;
301 }
302 }
303
Tyler Gunn8d83fa92014-07-01 11:31:21 -0700304 /** {@inheritDoc} */
305 @Override
306 public void onFeaturesChanged(Connection c, int features) {
307 String id = mIdByConnection.get(c);
308 Log.d(this, "Adapter set features %d", features);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700309 mAdapter.setFeatures(id, features);
Tyler Gunn8d83fa92014-07-01 11:31:21 -0700310 }
311
Ihab Awad542e0ea2014-05-16 10:22:16 -0700312 @Override
313 public void onDisconnected(Connection c, int cause, String message) {
314 String id = mIdByConnection.get(c);
Ihab Awad60ac30b2014-05-20 22:32:12 -0700315 Log.d(this, "Adapter set disconnected %d %s", cause, message);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700316 mAdapter.setDisconnected(id, cause, message);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700317 }
318
319 @Override
320 public void onHandleChanged(Connection c, Uri newHandle) {
321 // TODO: Unsupported yet
322 }
323
324 @Override
Ihab Awad542e0ea2014-05-16 10:22:16 -0700325 public void onSignalChanged(Connection c, Bundle details) {
326 // TODO: Unsupported yet
327 }
328
329 @Override
330 public void onDestroyed(Connection c) {
331 removeConnection(c);
332 }
Ihab Awadf8358972014-05-28 16:46:42 -0700333
334 @Override
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700335 public void onPostDialWait(Connection c, String remaining) {
Sailesh Nepal091768c2014-06-30 15:15:23 -0700336 String id = mIdByConnection.get(c);
337 Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700338 mAdapter.onPostDialWait(id, remaining);
Sailesh Nepal091768c2014-06-30 15:15:23 -0700339 }
340
341 @Override
Ihab Awadf8358972014-05-28 16:46:42 -0700342 public void onRequestingRingback(Connection c, boolean ringback) {
343 String id = mIdByConnection.get(c);
344 Log.d(this, "Adapter onRingback %b", ringback);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700345 mAdapter.setRequestingRingback(id, ringback);
Ihab Awadf8358972014-05-28 16:46:42 -0700346 }
Santos Cordonb6939982014-06-04 20:20:58 -0700347
348 @Override
349 public void onConferenceCapableChanged(Connection c, boolean isConferenceCapable) {
350 String id = mIdByConnection.get(c);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700351 mAdapter.setCanConference(id, isConferenceCapable);
Santos Cordonb6939982014-06-04 20:20:58 -0700352 }
353
354 /** ${inheritDoc} */
355 @Override
356 public void onParentConnectionChanged(Connection c, Connection parent) {
357 String id = mIdByConnection.get(c);
358 String parentId = parent == null ? null : mIdByConnection.get(parent);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700359 mAdapter.setIsConferenced(id, parentId);
Santos Cordonb6939982014-06-04 20:20:58 -0700360 }
Andrew Lee5ffbe8b2014-06-20 16:29:33 -0700361
362 @Override
363 public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {
364 String id = mIdByConnection.get(c);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700365 mAdapter.setCallVideoProvider(id, callVideoProvider);
Andrew Lee5ffbe8b2014-06-20 16:29:33 -0700366 }
Sailesh Nepal33aaae42014-07-07 22:49:44 -0700367
368 @Override
369 public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {
370 String id = mIdByConnection.get(c);
371 mAdapter.setAudioModeIsVoip(id, isVoip);
372 }
Sailesh Nepale7ef59a2014-07-08 21:48:22 -0700373
374 @Override
375 public void onSetStatusHints(Connection c, StatusHints statusHints) {
376 String id = mIdByConnection.get(c);
377 mAdapter.setStatusHints(id, statusHints);
378 }
Ihab Awad542e0ea2014-05-16 10:22:16 -0700379 };
380
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700381 /** {@inheritDoc} */
Ihab Awad542e0ea2014-05-16 10:22:16 -0700382 @Override
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700383 public final IBinder onBind(Intent intent) {
384 return mBinder;
385 }
386
387 private void call(final ConnectionRequest originalRequest) {
388 Log.d(this, "call %s", originalRequest);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700389 onCreateConnections(
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700390 originalRequest,
Sailesh Nepal506e3862014-06-25 13:35:14 -0700391 new OutgoingCallResponse<Connection>() {
Ihab Awad542e0ea2014-05-16 10:22:16 -0700392 @Override
Sailesh Nepal506e3862014-06-25 13:35:14 -0700393 public void onSuccess(ConnectionRequest request, Connection connection) {
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700394 Log.d(this, "adapter handleSuccessfulOutgoingCall %s", request.getCallId());
395 mAdapter.handleSuccessfulOutgoingCall(request);
396 addConnection(request.getCallId(), connection);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700397 }
398
399 @Override
Sailesh Nepal506e3862014-06-25 13:35:14 -0700400 public void onFailure(ConnectionRequest request, int code, String msg) {
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700401 mAdapter.handleFailedOutgoingCall(request, code, msg);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700402 }
Sailesh Nepal506e3862014-06-25 13:35:14 -0700403
404 @Override
405 public void onCancel(ConnectionRequest request) {
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700406 mAdapter.cancelOutgoingCall(request);
Sailesh Nepal506e3862014-06-25 13:35:14 -0700407 }
Ihab Awad542e0ea2014-05-16 10:22:16 -0700408 }
409 );
410 }
411
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700412 private void abort(String callId) {
Ihab Awad60ac30b2014-05-20 22:32:12 -0700413 Log.d(this, "abort %s", callId);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700414 findConnectionForAction(callId, "abort").onAbort();
Ihab Awad542e0ea2014-05-16 10:22:16 -0700415 }
416
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700417 private void createIncomingCall(ConnectionRequest originalRequest) {
418 Log.d(this, "createIncomingCall %s", originalRequest);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700419 onCreateIncomingConnection(
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700420 originalRequest,
Ihab Awad542e0ea2014-05-16 10:22:16 -0700421 new Response<ConnectionRequest, Connection>() {
422 @Override
423 public void onResult(ConnectionRequest request, Connection... result) {
Santos Cordonb6939982014-06-04 20:20:58 -0700424 if (result != null && result.length != 1) {
Ihab Awad542e0ea2014-05-16 10:22:16 -0700425 for (Connection c : result) {
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700426 c.onAbort();
Ihab Awad542e0ea2014-05-16 10:22:16 -0700427 }
428 } else {
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700429 addConnection(request.getCallId(), result[0]);
430 Log.d(this, "adapter notifyIncomingCall %s", request);
431 mAdapter.notifyIncomingCall(request);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700432 }
433 }
434
435 @Override
Ihab Awadfc91b7d2014-06-03 18:40:45 -0700436 public void onError(ConnectionRequest request, int code, String msg) {
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700437 Log.d(this, "adapter failed createIncomingCall %s %d %s",
Ihab Awadfc91b7d2014-06-03 18:40:45 -0700438 request, code, msg);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700439 }
440 }
441 );
442 }
443
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700444 private void answer(String callId) {
Ihab Awad60ac30b2014-05-20 22:32:12 -0700445 Log.d(this, "answer %s", callId);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700446 findConnectionForAction(callId, "answer").onAnswer();
Ihab Awad542e0ea2014-05-16 10:22:16 -0700447 }
448
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700449 private void reject(String callId) {
Ihab Awad60ac30b2014-05-20 22:32:12 -0700450 Log.d(this, "reject %s", callId);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700451 findConnectionForAction(callId, "reject").onReject();
Ihab Awad542e0ea2014-05-16 10:22:16 -0700452 }
453
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700454 private void disconnect(String callId) {
Ihab Awad60ac30b2014-05-20 22:32:12 -0700455 Log.d(this, "disconnect %s", callId);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700456 findConnectionForAction(callId, "disconnect").onDisconnect();
Ihab Awad542e0ea2014-05-16 10:22:16 -0700457 }
458
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700459 private void hold(String callId) {
Ihab Awad60ac30b2014-05-20 22:32:12 -0700460 Log.d(this, "hold %s", callId);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700461 findConnectionForAction(callId, "hold").onHold();
Ihab Awad542e0ea2014-05-16 10:22:16 -0700462 }
463
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700464 private void unhold(String callId) {
Ihab Awad60ac30b2014-05-20 22:32:12 -0700465 Log.d(this, "unhold %s", callId);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700466 findConnectionForAction(callId, "unhold").onUnhold();
Ihab Awad542e0ea2014-05-16 10:22:16 -0700467 }
468
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700469 private void onAudioStateChanged(String callId, CallAudioState audioState) {
Ihab Awad60ac30b2014-05-20 22:32:12 -0700470 Log.d(this, "onAudioStateChanged %s %s", callId, audioState);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700471 findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
472 }
473
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700474 private void playDtmfTone(String callId, char digit) {
475 Log.d(this, "playDtmfTone %s %c", callId, digit);
476 findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
477 }
478
479 private void stopDtmfTone(String callId) {
480 Log.d(this, "stopDtmfTone %s", callId);
481 findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
482 }
483
484 private void conference(final String conferenceCallId, String callId) {
Santos Cordonb6939982014-06-04 20:20:58 -0700485 Log.d(this, "conference %s, %s", conferenceCallId, callId);
Santos Cordon980acb92014-05-31 10:31:19 -0700486
Santos Cordonb6939982014-06-04 20:20:58 -0700487 Connection connection = findConnectionForAction(callId, "conference");
488 if (connection == NULL_CONNECTION) {
489 Log.w(this, "Connection missing in conference request %s.", callId);
490 return;
Santos Cordon980acb92014-05-31 10:31:19 -0700491 }
492
Santos Cordonb6939982014-06-04 20:20:58 -0700493 onCreateConferenceConnection(conferenceCallId, connection,
494 new Response<String, Connection>() {
495 /** ${inheritDoc} */
496 @Override
497 public void onResult(String ignored, Connection... result) {
498 Log.d(this, "onCreateConference.Response %s", (Object[]) result);
499 if (result != null && result.length == 1) {
500 Connection conferenceConnection = result[0];
501 if (!mIdByConnection.containsKey(conferenceConnection)) {
502 Log.v(this, "sending new conference call %s", conferenceCallId);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700503 mAdapter.addConferenceCall(conferenceCallId);
Santos Cordonb6939982014-06-04 20:20:58 -0700504 addConnection(conferenceCallId, conferenceConnection);
505 }
506 }
507 }
508
509 /** ${inheritDoc} */
510 @Override
511 public void onError(String request, int code, String reason) {
512 // no-op
513 }
514 });
Santos Cordon980acb92014-05-31 10:31:19 -0700515 }
516
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700517 private void splitFromConference(String callId) {
Santos Cordonb6939982014-06-04 20:20:58 -0700518 Log.d(this, "splitFromConference(%s)", callId);
Santos Cordon980acb92014-05-31 10:31:19 -0700519
520 Connection connection = findConnectionForAction(callId, "splitFromConference");
521 if (connection == NULL_CONNECTION) {
522 Log.w(this, "Connection missing in conference request %s.", callId);
523 return;
524 }
525
526 // TODO(santoscordon): Find existing conference call and invoke split(connection).
527 }
528
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700529 private void onPostDialContinue(String callId, boolean proceed) {
Evan Charlton6dea4ac2014-06-03 14:07:13 -0700530 Log.d(this, "onPostDialContinue(%s)", callId);
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700531 findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
Evan Charlton6dea4ac2014-06-03 14:07:13 -0700532 }
533
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700534 private void onPhoneAccountClicked(String callId) {
Sailesh Nepal2bed9562014-07-02 21:26:12 -0700535 Log.d(this, "onPhoneAccountClicked %s", callId);
536 findConnectionForAction(callId, "onPhoneAccountClicked").onPhoneAccountClicked();
537 }
538
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700539 private void onAdapterAttached() {
Ihab Awad9c3f1882014-06-30 21:17:13 -0700540 if (mAreAccountsInitialized) {
Santos Cordon52d8a152014-06-17 19:08:45 -0700541 // No need to query again if we already did it.
542 return;
543 }
544
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700545 mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
Santos Cordon52d8a152014-06-17 19:08:45 -0700546 @Override
547 public void onResult(
548 final List<ComponentName> componentNames,
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700549 final List<IBinder> services) {
Santos Cordon52d8a152014-06-17 19:08:45 -0700550 mHandler.post(new Runnable() {
551 @Override public void run() {
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700552 for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
Santos Cordon52d8a152014-06-17 19:08:45 -0700553 mRemoteConnectionManager.addConnectionService(
554 componentNames.get(i),
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700555 IConnectionService.Stub.asInterface(services.get(i)));
Santos Cordon52d8a152014-06-17 19:08:45 -0700556 }
Ihab Awad9c3f1882014-06-30 21:17:13 -0700557 mAreAccountsInitialized = true;
Sailesh Nepal2a46b902014-07-04 17:21:07 -0700558 Log.d(this, "remote call services found: " + services);
Ihab Awad9c3f1882014-06-30 21:17:13 -0700559 maybeRespondToAccountLookup();
Santos Cordon52d8a152014-06-17 19:08:45 -0700560 }
561 });
562 }
563
564 @Override
565 public void onError() {
566 mHandler.post(new Runnable() {
567 @Override public void run() {
Ihab Awad9c3f1882014-06-30 21:17:13 -0700568 mAreAccountsInitialized = true;
569 maybeRespondToAccountLookup();
Santos Cordon52d8a152014-06-17 19:08:45 -0700570 }
571 });
572 }
573 });
574 }
575
Ihab Awad9c3f1882014-06-30 21:17:13 -0700576 public final void lookupRemoteAccounts(
577 Uri handle, SimpleResponse<Uri, List<PhoneAccount>> response) {
578 mAccountLookupResponse = response;
579 mAccountLookupHandle = handle;
580 maybeRespondToAccountLookup();
Santos Cordon52d8a152014-06-17 19:08:45 -0700581 }
582
Ihab Awad9c3f1882014-06-30 21:17:13 -0700583 public final void maybeRespondToAccountLookup() {
584 if (mAreAccountsInitialized && mAccountLookupResponse != null) {
585 mAccountLookupResponse.onResult(
586 mAccountLookupHandle,
587 mRemoteConnectionManager.getAccounts(mAccountLookupHandle));
Santos Cordon52d8a152014-06-17 19:08:45 -0700588
Ihab Awad9c3f1882014-06-30 21:17:13 -0700589 mAccountLookupHandle = null;
590 mAccountLookupResponse = null;
Santos Cordon52d8a152014-06-17 19:08:45 -0700591 }
592 }
593
Sailesh Nepal091768c2014-06-30 15:15:23 -0700594 public final void createRemoteOutgoingConnection(
Santos Cordon52d8a152014-06-17 19:08:45 -0700595 ConnectionRequest request,
Sailesh Nepal506e3862014-06-25 13:35:14 -0700596 OutgoingCallResponse<RemoteConnection> response) {
Santos Cordon52d8a152014-06-17 19:08:45 -0700597 mRemoteConnectionManager.createOutgoingConnection(request, response);
598 }
599
600 /**
Santos Cordonb6939982014-06-04 20:20:58 -0700601 * Returns all connections currently associated with this connection service.
602 */
Sailesh Nepal091768c2014-06-30 15:15:23 -0700603 public final Collection<Connection> getAllConnections() {
Santos Cordonb6939982014-06-04 20:20:58 -0700604 return mConnectionById.values();
605 }
606
607 /**
Ihab Awad542e0ea2014-05-16 10:22:16 -0700608 * Create a Connection given a request.
609 *
610 * @param request Data encapsulating details of the desired Connection.
611 * @param callback A callback for providing the result.
612 */
Sailesh Nepal091768c2014-06-30 15:15:23 -0700613 protected void onCreateConnections(
Ihab Awad542e0ea2014-05-16 10:22:16 -0700614 ConnectionRequest request,
Sailesh Nepal506e3862014-06-25 13:35:14 -0700615 OutgoingCallResponse<Connection> callback) {}
Ihab Awad542e0ea2014-05-16 10:22:16 -0700616
617 /**
Santos Cordonb6939982014-06-04 20:20:58 -0700618 * Returns a new or existing conference connection when the the user elects to convert the
619 * specified connection into a conference call. The specified connection can be any connection
620 * which had previously specified itself as conference-capable including both simple connections
621 * and connections previously returned from this method.
622 *
623 * @param connection The connection from which the user opted to start a conference call.
624 * @param token The token to be passed into the response callback.
625 * @param callback The callback for providing the potentially-new conference connection.
626 */
Sailesh Nepal091768c2014-06-30 15:15:23 -0700627 protected void onCreateConferenceConnection(
Santos Cordonb6939982014-06-04 20:20:58 -0700628 String token,
629 Connection connection,
630 Response<String, Connection> callback) {}
631
632 /**
Ihab Awad542e0ea2014-05-16 10:22:16 -0700633 * Create a Connection to match an incoming connection notification.
634 *
Ihab Awadc0677542014-06-10 13:29:47 -0700635 * IMPORTANT: If the incoming connection has a phone number (or other handle) that the user
636 * is not supposed to be able to see (e.g. it is PRESENTATION_RESTRICTED), then a compliant
637 * ConnectionService implementation MUST NOT reveal this phone number as part of the Intent
638 * it sends to notify Telecomm of an incoming connection.
639 *
Ihab Awad542e0ea2014-05-16 10:22:16 -0700640 * @param request Data encapsulating details of the desired Connection.
641 * @param callback A callback for providing the result.
642 */
Sailesh Nepal091768c2014-06-30 15:15:23 -0700643 protected void onCreateIncomingConnection(
Ihab Awad542e0ea2014-05-16 10:22:16 -0700644 ConnectionRequest request,
645 Response<ConnectionRequest, Connection> callback) {}
646
Santos Cordonb6939982014-06-04 20:20:58 -0700647 /**
648 * Notifies that a connection has been added to this connection service and sent to Telecomm.
649 *
650 * @param connection The connection which was added.
651 */
Sailesh Nepal091768c2014-06-30 15:15:23 -0700652 protected void onConnectionAdded(Connection connection) {}
Santos Cordonb6939982014-06-04 20:20:58 -0700653
654 /**
655 * Notified that a connection has been removed from this connection service.
656 *
657 * @param connection The connection which was removed.
658 */
Sailesh Nepal091768c2014-06-30 15:15:23 -0700659 protected void onConnectionRemoved(Connection connection) {}
Santos Cordonb6939982014-06-04 20:20:58 -0700660
Ihab Awad542e0ea2014-05-16 10:22:16 -0700661 static String toLogSafePhoneNumber(String number) {
662 // For unknown number, log empty string.
663 if (number == null) {
664 return "";
665 }
666
667 if (PII_DEBUG) {
668 // When PII_DEBUG is true we emit PII.
669 return number;
670 }
671
672 // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
673 // sanitized phone numbers.
674 StringBuilder builder = new StringBuilder();
675 for (int i = 0; i < number.length(); i++) {
676 char c = number.charAt(i);
677 if (c == '-' || c == '@' || c == '.') {
678 builder.append(c);
679 } else {
680 builder.append('x');
681 }
682 }
683 return builder.toString();
684 }
685
Ihab Awad542e0ea2014-05-16 10:22:16 -0700686 private void addConnection(String callId, Connection connection) {
687 mConnectionById.put(callId, connection);
688 mIdByConnection.put(connection, callId);
689 connection.addConnectionListener(mConnectionListener);
Santos Cordonb6939982014-06-04 20:20:58 -0700690 onConnectionAdded(connection);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700691 }
692
693 private void removeConnection(Connection connection) {
694 connection.removeConnectionListener(mConnectionListener);
695 mConnectionById.remove(mIdByConnection.get(connection));
696 mIdByConnection.remove(connection);
Santos Cordonb6939982014-06-04 20:20:58 -0700697 onConnectionRemoved(connection);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700698 }
699
700 private Connection findConnectionForAction(String callId, String action) {
701 if (mConnectionById.containsKey(callId)) {
702 return mConnectionById.get(callId);
703 }
Ihab Awad60ac30b2014-05-20 22:32:12 -0700704 Log.w(this, "%s - Cannot find Connection %s", action, callId);
Ihab Awad542e0ea2014-05-16 10:22:16 -0700705 return NULL_CONNECTION;
706 }
Santos Cordon980acb92014-05-31 10:31:19 -0700707}