1 /*
2  * Copyright (C) 2015 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 android.telecom.cts;
18 
19 import android.content.Intent;
20 import android.telecom.Conference;
21 import android.telecom.Connection;
22 import android.telecom.ConnectionRequest;
23 import android.telecom.ConnectionService;
24 import android.telecom.PhoneAccountHandle;
25 import android.telecom.RemoteConference;
26 import android.telecom.RemoteConnection;
27 import android.util.Log;
28 
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.concurrent.CountDownLatch;
32 
33 /**
34  * This is the official ConnectionService for Telecom's CTS App. Since telecom requires that a
35  * CS be registered in the AndroidManifest.xml file, we have to have a single implementation
36  * of a CS and this is it. To test specific CS behavior, tests will implement their own CS and
37  * tell CtsConnectionService to forward any method invocations to that test's implementation.
38  * This is set up using {@link #setUp} and should be cleaned up before the end of the test using
39  * {@link #tearDown}.
40  *
41  * sConnectionService: Contains the connection service object provided by the current test in
42  *                     progress. We use this object to forward any communication received from the
43  *                     Telecom framework to the test connection service.
44  * sTelecomConnectionService: Contains the connection service object registered to the Telecom
45  *                            framework. We use this object to forward any communication from the
46  *                            test connection service to the Telecom framework. After Telecom
47  *                            binds to CtsConnectionService, this is set to be the instance of
48  *                            CtsConnectionService created by the framework after Telecom binds.
49  */
50 public class CtsConnectionService extends ConnectionService {
51     private static String LOG_TAG = "CtsConnectionService";
52     // This is the connection service implementation set locally during test setup. Telecom calls
53     // these overwritten methods.
54     private static ConnectionService sConnectionServiceTestImpl;
55     // Represents the connection from the test ConnectionService to telecom. Only valid once telecom
56     // has successfully bound.
57     private static ConnectionService sTelecomConnectionService;
58     private static CountDownLatch sTelecomUnboundLatch;
59     // Lock managing the setup and usage of sConnectionServiceTestImpl.
60     private static final Object sTestImplLock = new Object();
61     // Lock managing the setup and usage of sTelecomConnectionService/sTelecomUnboundLatch.
62     private static final Object sTelecomCSLock = new Object();
63 
64     @Override
onBindClient(Intent intent)65     public void onBindClient(Intent intent) {
66         Log.i("TelecomCTS", "CS bound");
67         onTelecomConnected(this);
68     }
69 
70     /**
71      * Call when a test is being setup to prepare this instance for testing.
72      * @param connectionService The ConnectionService impl to use to proxy commands to from telecom.
73      * @throws Exception There was an illegal state that caused the setup to fail.
74      */
setUp(ConnectionService connectionService)75     public static void setUp(ConnectionService connectionService) throws Exception {
76         setTestImpl(connectionService);
77     }
78 
79     /**
80      * Call when a test is complete to tear down.
81      */
tearDown()82     public static void tearDown() {
83         clearTestImpl();
84     }
85 
86     @Override
onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)87     public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
88             ConnectionRequest request) {
89         ConnectionService testImpl = getTestImpl();
90         if (testImpl != null) {
91             return testImpl.onCreateOutgoingConnection(connectionManagerPhoneAccount, request);
92         } else {
93             Log.e(LOG_TAG,
94                     "Tried to create outgoing connection when sConnectionService null!");
95             return null;
96         }
97     }
98 
99     @Override
onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)100     public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
101             ConnectionRequest request) {
102         ConnectionService testImpl = getTestImpl();
103         if (testImpl != null) {
104             return testImpl.onCreateIncomingConnection(connectionManagerPhoneAccount, request);
105         } else {
106             Log.e(LOG_TAG,
107                     "Tried to create incoming connection when sConnectionService null!");
108             return null;
109         }
110     }
111 
112     @Override
onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)113     public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
114             ConnectionRequest request) {
115         ConnectionService testImpl = getTestImpl();
116         if (testImpl != null) {
117             testImpl.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request);
118         } else {
119             Log.e(LOG_TAG, "onCreateIncomingConnectionFailed called when "
120                     + "sConnectionService null!");
121         }
122     }
123 
124     @Override
onCreateOutgoingConference(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)125     public Conference onCreateOutgoingConference(PhoneAccountHandle connectionManagerPhoneAccount,
126             ConnectionRequest request) {
127         ConnectionService testImpl = getTestImpl();
128         if (testImpl != null) {
129             return testImpl.onCreateOutgoingConference(connectionManagerPhoneAccount, request);
130         } else {
131             Log.e(LOG_TAG,
132                     "onCreateOutgoingConference called when sConnectionService null!");
133             return null;
134         }
135     }
136 
137     @Override
onCreateOutgoingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)138     public void onCreateOutgoingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount,
139             ConnectionRequest request) {
140         ConnectionService testImpl = getTestImpl();
141         if (testImpl != null) {
142             testImpl.onCreateOutgoingConferenceFailed(connectionManagerPhoneAccount, request);
143         } else {
144             Log.e(LOG_TAG,
145                     "onCreateOutgoingConferenceFailed called when sConnectionService null!");
146         }
147     }
148 
149     @Override
onCreateIncomingConference(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)150     public Conference onCreateIncomingConference(PhoneAccountHandle connectionManagerPhoneAccount,
151             ConnectionRequest request) {
152         ConnectionService testImpl = getTestImpl();
153         if (testImpl != null) {
154             return testImpl.onCreateIncomingConference(connectionManagerPhoneAccount, request);
155         } else {
156             Log.e(LOG_TAG,
157                     "onCreateIncomingConference called when sConnectionService null!");
158             return null;
159         }
160     }
161 
162     @Override
onCreateConnectionComplete(Connection connection)163     public void onCreateConnectionComplete(Connection connection) {
164         ConnectionService testImpl = getTestImpl();
165         if (testImpl != null) {
166             testImpl.onCreateConnectionComplete(connection);
167         } else {
168             Log.e(LOG_TAG, "onCreateConnectionComplete called when "
169                     + "sConnectionService null!");
170         }
171     }
172 
173     @Override
onCreateConferenceComplete(Conference conference)174     public void onCreateConferenceComplete(Conference conference) {
175         ConnectionService testImpl = getTestImpl();
176         if (testImpl != null) {
177             testImpl.onCreateConferenceComplete(conference);
178         } else {
179             Log.e(LOG_TAG, "onCreateConferenceComplete called when "
180                     + "sConnectionService null!");
181         }
182     }
183 
184     @Override
onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)185     public void onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount,
186             ConnectionRequest request) {
187         ConnectionService testImpl = getTestImpl();
188         if (testImpl != null) {
189             testImpl.onCreateIncomingConferenceFailed(connectionManagerPhoneAccount, request);
190         } else {
191             Log.e(LOG_TAG,
192                     "onCreateIncomingConferenceFailed called when sConnectionService null!");
193         }
194     }
195 
196     @Override
onConference(Connection connection1, Connection connection2)197     public void onConference(Connection connection1, Connection connection2) {
198         ConnectionService testImpl = getTestImpl();
199         if (testImpl != null) {
200             testImpl.onConference(connection1, connection2);
201         } else {
202             Log.e(LOG_TAG,
203                     "onConference called when sConnectionService null!");
204         }
205     }
206 
207     @Override
onRemoteExistingConnectionAdded(RemoteConnection connection)208     public void onRemoteExistingConnectionAdded(RemoteConnection connection) {
209         ConnectionService testImpl = getTestImpl();
210         if (testImpl != null) {
211             testImpl.onRemoteExistingConnectionAdded(connection);
212         } else {
213             Log.e(LOG_TAG,
214                     "onRemoteExistingConnectionAdded called when sConnectionService null!");
215         }
216     }
217 
addConferenceToTelecom(Conference conference)218     public static void addConferenceToTelecom(Conference conference) {
219         ConnectionService telecomConn = getTelecomConnection();
220         if (telecomConn != null) {
221             telecomConn.addConference(conference);
222         } else {
223             Log.e(LOG_TAG, "addConferenceToTelecom called when"
224                     + " sTelecomConnectionService null!");
225         }
226     }
227 
addExistingConnectionToTelecom( PhoneAccountHandle phoneAccountHandle, Connection connection)228     public static void addExistingConnectionToTelecom(
229             PhoneAccountHandle phoneAccountHandle, Connection connection) {
230         ConnectionService telecomConn = getTelecomConnection();
231         if (telecomConn != null) {
232             telecomConn.addExistingConnection(phoneAccountHandle, connection);
233         } else {
234             Log.e(LOG_TAG, "addExistingConnectionToTelecom called when"
235                     + " sTelecomConnectionService null!");
236         }
237     }
238 
getAllConnectionsFromTelecom()239     public static Collection<Connection> getAllConnectionsFromTelecom() {
240         ConnectionService telecomConn = getTelecomConnection();
241         if (telecomConn == null) {
242             return Collections.emptyList();
243         }
244         return telecomConn.getAllConnections();
245     }
246 
createRemoteOutgoingConnectionToTelecom( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)247     public static RemoteConnection createRemoteOutgoingConnectionToTelecom(
248             PhoneAccountHandle connectionManagerPhoneAccount,
249             ConnectionRequest request) {
250         ConnectionService telecomConn = getTelecomConnection();
251         if (telecomConn != null) {
252             return telecomConn.createRemoteOutgoingConnection(
253                     connectionManagerPhoneAccount, request);
254         } else {
255             Log.e(LOG_TAG, "createRemoteOutgoingConnectionToTelecom called when"
256                     + " sTelecomConnectionService null!");
257             return null;
258         }
259     }
260 
createRemoteIncomingConnectionToTelecom( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)261     public static RemoteConnection createRemoteIncomingConnectionToTelecom(
262             PhoneAccountHandle connectionManagerPhoneAccount,
263             ConnectionRequest request) {
264         ConnectionService telecomConn = getTelecomConnection();
265         if (telecomConn != null) {
266             return telecomConn.createRemoteIncomingConnection(
267                     connectionManagerPhoneAccount, request);
268         } else {
269             Log.e(LOG_TAG, "createRemoteIncomingConnectionToTelecom called when"
270                     + " sTelecomConnectionService null!");
271             return null;
272         }
273     }
274 
createRemoteIncomingConferenceToTelecom( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)275     public static RemoteConference createRemoteIncomingConferenceToTelecom(
276             PhoneAccountHandle connectionManagerPhoneAccount,
277             ConnectionRequest request) {
278         ConnectionService telecomConn = getTelecomConnection();
279         if (telecomConn != null) {
280             return telecomConn.createRemoteIncomingConference(
281                     connectionManagerPhoneAccount, request);
282         } else {
283             Log.e(LOG_TAG, "createRemoteIncomingConferenceToTelecom called when"
284                     + " sTelecomConnectionService null!");
285             return null;
286         }
287     }
288 
289 
createRemoteOutgoingConferenceToTelecom( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)290     public static RemoteConference createRemoteOutgoingConferenceToTelecom(
291             PhoneAccountHandle connectionManagerPhoneAccount,
292             ConnectionRequest request) {
293         ConnectionService telecomConn = getTelecomConnection();
294         if (telecomConn != null) {
295             return telecomConn.createRemoteOutgoingConference(
296                     connectionManagerPhoneAccount, request);
297         } else {
298             Log.e(LOG_TAG, "createRemoteOutgoingConferenceToTelecom called when"
299                     + " sTelecomConnectionService null!");
300             return null;
301         }
302     }
303 
304     @Override
onRemoteConferenceAdded(RemoteConference conference)305     public void onRemoteConferenceAdded(RemoteConference conference) {
306         ConnectionService telecomConn = getTestImpl();
307         if (telecomConn != null) {
308             telecomConn.onRemoteConferenceAdded(conference);
309         } else {
310             Log.e(LOG_TAG, "onRemoteConferenceAdded called when sConnectionService null!");
311         }
312     }
313 
314     @Override
onConnectionServiceFocusGained()315     public void onConnectionServiceFocusGained() {
316         ConnectionService telecomConn = getTestImpl();
317         if (telecomConn != null) {
318             telecomConn.onConnectionServiceFocusGained();
319         } else {
320             Log.e(LOG_TAG, "onConnectionServiceFocusGained called when sConnectionService null!");
321         }
322     }
323 
324     @Override
onConnectionServiceFocusLost()325     public void onConnectionServiceFocusLost() {
326         ConnectionService telecomConn = getTestImpl();
327         if (telecomConn != null) {
328             telecomConn.onConnectionServiceFocusLost();
329         } else {
330             Log.e(LOG_TAG, "onConnectionServiceFocusLost called when sConnectionService null!");
331         }
332     }
333 
334     @Override
onUnbind(Intent intent)335     public boolean onUnbind(Intent intent) {
336         Log.i(LOG_TAG, "Service has been unbound");
337         onTelecomDisconnected();
338         return super.onUnbind(intent);
339     }
340 
isServiceRegisteredToTelecom()341     public static boolean isServiceRegisteredToTelecom() {
342         return getTelecomConnection() != null;
343     }
344 
345     /**
346      * @return Wait up to 5 seconds for the ConnectionService to be unbound from telecom. Return
347      * true if unbinding occurred successfully, false if it did not.
348      */
waitForUnBinding()349     public static boolean waitForUnBinding() {
350         CountDownLatch latch = getUnboundLatch();
351         return TestUtils.waitForLatchCountDown(latch);
352     }
353 
354     /**
355      * Setup the static connection to the ConnectionService that is handling commands to Telecom
356      * through ConnectionService.
357      */
onTelecomConnected(ConnectionService service)358     private static void onTelecomConnected(ConnectionService service) {
359         synchronized (sTelecomCSLock) {
360             sTelecomConnectionService = service;
361             if (sTelecomUnboundLatch != null && sTelecomUnboundLatch.getCount() > 0) {
362                 Log.w(LOG_TAG, "Unexpected: Unbound latch has not counted down from previous "
363                         + "usage");
364             }
365             sTelecomUnboundLatch = new CountDownLatch(1);
366         }
367     }
368 
369     /**
370      * Teardown a previously connected ConnectionService when Telecom unbinds.
371      */
onTelecomDisconnected()372     private static void onTelecomDisconnected() {
373         synchronized (sTelecomCSLock) {
374             if (sTelecomUnboundLatch != null) {
375                 sTelecomUnboundLatch.countDown();
376             } else {
377                 Log.w(LOG_TAG, "Unexpected: null unbind latch, onBindClient never called.");
378             }
379             sTelecomConnectionService = null;
380         }
381     }
382 
383     /**
384      * @return The ConnectionService that Telecom is connected through right now. This
385      * ConnectionService must only be used to communicate back to Telecom. This must be called to
386      * get the ConnectionService instance in order to keep synchronization across threads.
387      */
getTelecomConnection()388     private static ConnectionService getTelecomConnection() {
389         synchronized (sTelecomCSLock) {
390             return sTelecomConnectionService;
391         }
392     }
393 
394     /**
395      * @return The CountDownLatch tracking when the ConnectionService will be unbound by Telecom.
396      */
getUnboundLatch()397     private static CountDownLatch getUnboundLatch() {
398         synchronized (sTelecomCSLock) {
399             return sTelecomUnboundLatch;
400         }
401     }
402 
403     /**
404      * @return The ConnectionService that the test impl has provided to implement stub methods.
405      * This must be called to get the ConnectionService instance in order to keep synchronization
406      * across threads.
407      */
getTestImpl()408     private static ConnectionService getTestImpl() {
409         synchronized (sTestImplLock) {
410             return sConnectionServiceTestImpl;
411         }
412     }
413 
414     /**
415      * Clear the ConnectionService when the test is tearing down.
416      */
clearTestImpl()417     private static void clearTestImpl() {
418         synchronized (sTestImplLock) {
419             sConnectionServiceTestImpl = null;
420         }
421     }
422 
423     /**
424      * Sets the Implementation of ConnectionService stub methods for this test. Must be called
425      * before using any ConnectionService based tests.
426      * @param impl The ConnectionService instance that implements ConnectionService stub methods
427      * @throws Exception Invalid state occurred
428      */
setTestImpl(ConnectionService impl)429     private static void setTestImpl(ConnectionService impl) throws Exception {
430         synchronized (sTestImplLock) {
431             if (sConnectionServiceTestImpl != null) {
432                 // Clean up so following tests don't fail too, hiding the original culprit in noise
433                 sConnectionServiceTestImpl = null;
434                 throw new Exception("Mock ConnectionService exists.  Failed to call setUp(), "
435                         + "or previous test failed to call tearDown().");
436             }
437             sConnectionServiceTestImpl = impl;
438         }
439     }
440 }
441