• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.apps;
18 
19 import static android.telecom.cts.apps.TelecomTestApp.ConnectionServiceVoipAppClone;
20 import static android.telecom.cts.apps.TelecomTestApp.ConnectionServiceVoipAppMain;
21 import static android.telecom.cts.apps.TelecomTestApp.TransactionalVoipAppClone;
22 import static android.telecom.cts.apps.TelecomTestApp.TransactionalVoipAppMain;
23 
24 import static org.junit.Assert.fail;
25 
26 import android.os.Bundle;
27 import android.os.DeadObjectException;
28 import android.os.RemoteException;
29 import android.os.UserHandle;
30 import android.telecom.CallAttributes;
31 import android.telecom.CallEndpoint;
32 import android.telecom.CallException;
33 import android.telecom.PhoneAccount;
34 import android.telecom.PhoneAccountHandle;
35 import android.util.Log;
36 
37 import androidx.test.platform.app.InstrumentationRegistry;
38 
39 import java.util.List;
40 import java.util.function.Consumer;
41 
42 public class AppControlWrapper {
43     private static final String TAG = AppControlWrapper.class.getSimpleName();
44     private static final String CLASS = AppControlWrapper.class.getCanonicalName();
45     private final IAppControl mBinder;
46     private final TelecomTestApp mTelecomApps;
47     private final boolean mIsManagedAppControl;
48     private final boolean mIsManagedAppControlClone;
49 
getTelecomApps()50     public TelecomTestApp getTelecomApps() {
51         return mTelecomApps;
52     }
53 
isManagedAppControl()54     public boolean isManagedAppControl() {
55         return mIsManagedAppControl;
56     }
57 
isManagedAppControlClone()58     public boolean isManagedAppControlClone() {
59         return mIsManagedAppControlClone;
60     }
61 
isManagedControl()62     public boolean isManagedControl() {
63         return isManagedAppControl() || isManagedAppControlClone();
64     }
65 
isTransactionalControl()66     public boolean isTransactionalControl() {
67         return mTelecomApps.equals(TransactionalVoipAppClone)
68                 || mTelecomApps.equals(TransactionalVoipAppMain);
69     }
70 
isVoipControl()71     public boolean isVoipControl() {
72         return mTelecomApps.equals(ConnectionServiceVoipAppMain)
73                 || mTelecomApps.equals(ConnectionServiceVoipAppClone)
74                 || isTransactionalControl();
75     }
76 
AppControlWrapper(IAppControl binder, TelecomTestApp name)77     public AppControlWrapper(IAppControl binder, TelecomTestApp name) {
78         mBinder = binder;
79         mTelecomApps = name;
80         if (mTelecomApps.equals(TelecomTestApp.ManagedConnectionServiceApp)) {
81             mIsManagedAppControl = true;
82             mIsManagedAppControlClone = false;
83         } else if (mTelecomApps.equals(TelecomTestApp.ManagedConnectionServiceAppClone)) {
84             mIsManagedAppControlClone = true;
85             mIsManagedAppControl = false;
86         } else {
87             mIsManagedAppControl = false;
88             mIsManagedAppControlClone = false;
89         }
90     }
91 
92     /**
93      * This method helps determine if the application attached to this wrapper is currently bound
94      * to. Typically this can help to determine if the test process needs to wait longer before
95      * calling binder methods.
96      *
97      * @return true if the telecom app is bound to at this current time. otherwise returns false.
98      */
isBound()99     public boolean isBound() {
100         Log.i(TAG, "isBound");
101         try {
102             return mBinder.isBound();
103         } catch (RemoteException e) {
104             Log.e(TAG, "failed to get isBound", e);
105         }
106         return false;
107     }
108 
getProcessUserHandle()109     public UserHandle getProcessUserHandle() {
110         Log.i(TAG, "getProcessUserHandle");
111         try {
112             return mBinder.getProcessUserHandle();
113         } catch (RemoteException e) {
114             Log.e(TAG, "failed to get getProcessUserHandle", e);
115         }
116         return null;
117     }
118 
119 
getProcessUid()120     public int getProcessUid() {
121         Log.i(TAG, "getProcessUserHandle");
122         try {
123             return mBinder.getProcessUid();
124         } catch (RemoteException e) {
125             Log.e(TAG, "failed to getProcessUid", e);
126         }
127         return -1;
128     }
129 
130     /**
131      * This method requests the app that is bound to add a new call with the given callAttributes.
132      * Note: This method does not verify the call is added for ConnectionService implementations
133      * and that job should be left for the InCallService to verify.
134      */
addCall(CallAttributes callAttributes)135     public void addCall(CallAttributes callAttributes) throws Exception {
136         Log.i(TAG, "addCall");
137         try {
138             NoDataTransaction transactionResult = mBinder.addCall(callAttributes);
139             maybeFailTest(transactionResult);
140         } catch (RemoteException re) {
141             handleRemoteException(re, "addCall");
142         }
143     }
144 
145     /**
146      * This method requests the app that is bound to add a new call with the given callAttributes
147      * that is expected to fail. Instead, it verifies whether the onCreateOutgoingConnection
148      * callback is invoked. Note: This method is purely being used to verify MMI code deflection
149      * behavior for DSDA cases and verifies that the callback wasn't invoked.
150      */
addFailedCallWithCreateConnectionVerify(CallAttributes callAttributes)151     public void addFailedCallWithCreateConnectionVerify(CallAttributes callAttributes)
152             throws Exception {
153         Log.i(TAG, "addFailedCallWithCreateConnectionVerify");
154         try {
155             NoDataTransaction transactionResult =
156                     mBinder.addFailedCallWithCreateConnectionVerify(callAttributes);
157             maybeFailTest(transactionResult);
158         } catch (RemoteException re) {
159             handleRemoteException(re, "addCall");
160         }
161     }
162 
163     /**
164      * This method requests the app that is bound to add a new call with the given callAttributes
165      * that is expected to fail.
166      */
addFailedCall(CallAttributes callAttributes)167     public void addFailedCall(CallAttributes callAttributes) throws Exception {
168         Log.i(TAG, "addFailedCall");
169         try {
170             NoDataTransaction transactionResult = mBinder.addFailedCall(callAttributes);
171             maybeFailTest(transactionResult);
172         } catch (RemoteException re) {
173             handleRemoteException(re, "addCall");
174         }
175     }
176 
177     /**
178      * This method requests the app that is bound to add a new call with the given callAttributes.
179      * Note: This method does not verify the call is added for ConnectionService implementations
180      * and that job should be left for the InCallService to verify.
181      */
addCall(CallAttributes callAttributes, Consumer<CallStateTransitionOperation> consumer)182     public void addCall(CallAttributes callAttributes,
183             Consumer<CallStateTransitionOperation> consumer) throws Exception {
184         Log.i(TAG, "addCall");
185         try {
186             NoDataTransaction transactionResult =
187                     mBinder.addCallWithConsumer(
188                             callAttributes,
189                             new IRemoteOperationConsumer.Stub() {
190                                 @Override
191                                 public void complete(CallStateTransitionOperation op) {
192                                     consumer.accept(op);
193                                 }
194                             });
195             maybeFailTest(transactionResult);
196         } catch (RemoteException re) {
197             handleRemoteException(re, "addCall");
198         }
199     }
200 
201     /**
202      * This method requests the app that is bound to add a new call with the given callAttributes.
203      * Note: This method does not verify the call is added for ConnectionService implementations and
204      * that job should be left for the InCallService to verify.
205      */
verifyAddCall( CallAttributes callAttributes, Consumer<CallStateTransitionOperation> consumer)206     public void verifyAddCall(
207             CallAttributes callAttributes, Consumer<CallStateTransitionOperation> consumer)
208             throws Exception {
209         Log.i(TAG, "verifyAddCall");
210         try {
211             NoDataTransaction transactionResult =
212                     mBinder.verifyCallWithConsumer(
213                             callAttributes,
214                             new IRemoteOperationConsumer.Stub() {
215                                 @Override
216                                 public void complete(CallStateTransitionOperation op) {
217                                     consumer.accept(op);
218                                 }
219                             });
220             maybeFailTest(transactionResult);
221         } catch (RemoteException re) {
222             handleRemoteException(re, "addCall");
223         }
224     }
225 
226     /**
227      * This method requests the app that is bound to transition the call state to newCallState
228      *
229      * @param expectSuccess is used for transactional applications only so that both the success and
230      *                      fail cases can be tested.
231      * @param extras        contains videoState and disconnectCause info.
232      */
setCallState(String id, int newCallState, boolean expectSuccess, Bundle extras)233     public CallException setCallState(String id,
234             int newCallState,
235             boolean expectSuccess,
236             Bundle extras) throws Exception {
237         Log.i(TAG, "setCallState");
238         try {
239             CallExceptionTransaction transactionResult =
240                     mBinder.transitionCallStateTo(id, newCallState, expectSuccess, extras);
241             maybeFailTest(transactionResult);
242             return transactionResult.getCallException();
243         } catch (RemoteException e) {
244             handleRemoteException(e, "setCallState");
245         }
246         return null;
247     }
248 
249     /**
250      * Waits for the current [CallEndpoint] to be non-null before returning. Otherwise, the
251      * application will throw an error.
252      */
getCurrentCallEndpoint(String id)253     public CallEndpoint getCurrentCallEndpoint(String id) throws Exception {
254         Log.i(TAG, "getCurrentCallEndpoint");
255         CallEndpointTransaction transactionResult = null;
256         try {
257             transactionResult = mBinder.getCurrentCallEndpoint(id);
258             maybeFailTest(transactionResult);
259         } catch (RemoteException e) {
260             handleRemoteException(e, "getCurrentCallEndpoint");
261         }
262         return transactionResult.getCallEndpoint();
263     }
264 
265     /**
266      * Waits for the available [CallEndpoint]s to be non-null before returning. Otherwise, the
267      * application will throw an error.
268      */
getAvailableCallEndpoints(String id)269     public List<CallEndpoint> getAvailableCallEndpoints(String id) throws Exception {
270         Log.i(TAG, "getAvailableCallEndpoints");
271         AvailableEndpointsTransaction transactionResult = null;
272         try {
273             transactionResult = mBinder.getAvailableCallEndpoints(id);
274             maybeFailTest(transactionResult);
275         } catch (RemoteException e) {
276             handleRemoteException(e, "getAvailableCallEndpoints");
277         }
278         return transactionResult.getCallEndpoint();
279     }
280 
281     /**
282      * switches the current [CallEndpoint]
283      */
setAudioRouteStateAndVerify(String id, CallEndpoint newCallEndpoint)284     public void setAudioRouteStateAndVerify(String id, CallEndpoint newCallEndpoint)
285             throws RemoteException {
286         Log.i(TAG, "setAudioRouteStateAndVerify");
287         try {
288             NoDataTransaction transactionResult =
289                     mBinder.requestCallEndpointChange(id, newCallEndpoint);
290             maybeFailTest(transactionResult);
291         } catch (RemoteException e) {
292             handleRemoteException(e, "setAudioRouteStateAndVerify");
293         }
294     }
295 
296     /**
297      * Gets the current mute value of the application. This is tracked locally and updated every
298      * time the audio state changes.
299      */
isMuted(String id)300     public boolean isMuted(String id) throws RemoteException {
301         Log.i(TAG, "isMuted");
302         try {
303             BooleanTransaction transactionResult = mBinder.isMuted(id);
304             maybeFailTest(transactionResult);
305             return transactionResult.getBoolResult();
306         } catch (RemoteException e) {
307             handleRemoteException(e, "isMuted");
308         }
309         return false;
310     }
311 
312     /**
313      * Sets the mute state
314      */
setMuteState(String id, boolean isMuted)315     public void setMuteState(String id, boolean isMuted) throws RemoteException {
316         Log.i(TAG, "setMuteState");
317         try {
318             NoDataTransaction transactionResult = mBinder.setMuteState(id, isMuted);
319             maybeFailTest(transactionResult);
320         } catch (RemoteException e) {
321             handleRemoteException(e, "setMuteState");
322         }
323     }
324 
325     /** Sends the specified connection event on the associated identifier for the call. */
sendConnectionEvent(String id, String event)326     public void sendConnectionEvent(String id, String event) throws RemoteException {
327         Log.i(TAG, "sendConnectionEvent");
328         try {
329             NoDataTransaction transactionResult = mBinder.sendConnectionEvent(id, event);
330             maybeFailTest(transactionResult);
331         } catch (RemoteException e) {
332             handleRemoteException(e, "sendConnectionEvent");
333         }
334     }
335 
336     /**
337      * Registers the default account that is defined in the application.info class that corresponds
338      * to the implementation class
339      */
registerDefaultPhoneAccount()340     public void registerDefaultPhoneAccount() throws RemoteException {
341         Log.i(TAG, "registerDefaultPhoneAccount");
342         try {
343             NoDataTransaction transactionResult = mBinder.registerDefaultPhoneAccount();
344             maybeFailTest(transactionResult);
345         } catch (RemoteException e) {
346             handleRemoteException(e, "registerDefaultPhoneAccount");
347         }
348     }
349 
350     /**
351      * Gets the default account that is defined in the application.info class that corresponds
352      * to the implementation class.
353      */
getDefaultPhoneAccount()354     public PhoneAccount getDefaultPhoneAccount() throws RemoteException {
355         Log.i(TAG, "getDefaultPhoneAccount");
356         try {
357             PhoneAccountTransaction transactionResult = mBinder.getDefaultPhoneAccount();
358             maybeFailTest(transactionResult);
359             return transactionResult.getPhoneAccount();
360         } catch (RemoteException e) {
361             handleRemoteException(e, "getDefaultPhoneAccount");
362         }
363         return null;
364     }
365 
366     /**
367      * Registers a custom account that is usually defined at the test class level.
368      */
registerCustomPhoneAccount(PhoneAccount account)369     public void registerCustomPhoneAccount(PhoneAccount account) throws RemoteException {
370         Log.i(TAG, "registerCustomPhoneAccount");
371         try {
372             mBinder.registerCustomPhoneAccount(account);
373         } catch (RemoteException e) {
374             handleRemoteException(e, "registerCustomPhoneAccount");
375         }
376     }
377 
378     /**
379      * Unregisters a given account from the client side
380      */
unregisterPhoneAccountWithHandle(PhoneAccountHandle handle)381     public void unregisterPhoneAccountWithHandle(PhoneAccountHandle handle) throws RemoteException {
382         Log.i(TAG, "unregisterPhoneAccountWithHandle");
383         try {
384             mBinder.unregisterPhoneAccountWithHandle(handle);
385         } catch (RemoteException e) {
386             handleRemoteException(e, "unregisterPhoneAccountWithHandle");
387         }
388     }
389 
390     /**
391      * Gets all the SELF_MANAGED accounts that are retrievable to the client! Helpful to see what
392      * accounts the client is unable to fetch.
393      */
getAccountHandlesForApp()394     public List<PhoneAccountHandle> getAccountHandlesForApp() throws RemoteException {
395         Log.i(TAG, "getAccountHandlesForApp");
396         try {
397             return mBinder.getOwnAccountHandlesForApp();
398         } catch (RemoteException e) {
399             handleRemoteException(e, "getAccountHandlesForApp");
400         }
401         return null;
402     }
403 
404     /**
405      * Fetch all the PhoneAccounts associated with the application.
406      */
getRegisteredPhoneAccounts()407     public List<PhoneAccount> getRegisteredPhoneAccounts() throws RemoteException {
408         Log.i(TAG, "getRegisteredPhoneAccounts");
409         try {
410             return mBinder.getRegisteredPhoneAccounts();
411         } catch (RemoteException e) {
412             handleRemoteException(e, "getRegisteredPhoneAccounts");
413         }
414         return null;
415     }
416 
handleRemoteException(RemoteException e, String callingMethod)417     private void handleRemoteException(RemoteException e, String callingMethod)
418             throws RemoteException {
419         // before failing the test or throwing the exception, attempt to clean-up the test app
420         // and dump the telecom state to help with debugging since test reports do not include the
421         // Telecom tab
422         dumpTelecomStateAndCleanupApp();
423         if (e.getClass().equals(DeadObjectException.class)) {
424             fail(CLASS + "." + callingMethod + " threw a DeadObjectException meaning that "
425                     + "Process=[" + mTelecomApps + "] died while processing the " + callingMethod
426                     + " binder transaction. Look at earlier logs to determine what caused the test"
427                     + "app to crash.");
428         } else {
429             throw e;
430         }
431     }
432 
dumpTelecomStateAndCleanupApp()433     private void dumpTelecomStateAndCleanupApp() {
434         try {
435             ShellCommandExecutor.dumpTelecom(InstrumentationRegistry.getInstrumentation());
436             mBinder.cleanup();
437         } catch (Exception cleanupException) {
438             // ignore exception while trying to clean up
439         }
440     }
441 
maybeFailTest(BaseTransaction transactionResult)442     private void maybeFailTest(BaseTransaction transactionResult) {
443         if (transactionResult != null
444                 && transactionResult.getResult().equals(TestAppTransaction.Failure)) {
445             // before failing the test or throwing the exception, attempt to clean-up the test app
446             // and dump the telecom state to help with debugging since test reports do not include
447             // the Telecom tab
448             dumpTelecomStateAndCleanupApp();
449             fail(transactionResult.getTestAppException().getMessage());
450         }
451     }
452 
isNotificationPostedForCall(String callId)453     public boolean isNotificationPostedForCall(String callId) throws RemoteException {
454         Log.i(TAG, "isNotificationPostedForCall");
455         try {
456             BooleanTransaction transactionResult = mBinder.isNotificationPostedForCall(callId);
457             maybeFailTest(transactionResult);
458             return transactionResult.getBoolResult();
459         } catch (RemoteException e) {
460            handleRemoteException(e, "isNotificationPostedForCall");
461         }
462         return false;
463     }
464 
removeNotificationForCall(String callId)465     public void removeNotificationForCall(String callId) throws RemoteException {
466         Log.i(TAG, "removeNotificationForCall");
467         try {
468             NoDataTransaction transactionResult = mBinder.removeNotificationForCall(callId);
469             maybeFailTest(transactionResult);
470         } catch (RemoteException e) {
471            handleRemoteException(e, "removeNotificationForCall");
472         }
473     }
474 
475     /** fetches the foreground service delegation state for a particular (app, handle) combo */
isForegroundServiceDelegationActive(PhoneAccountHandle handle)476     public boolean isForegroundServiceDelegationActive(PhoneAccountHandle handle)
477             throws RemoteException {
478         try {
479             BooleanTransaction transactionResult =
480                     mBinder.isForegroundServiceDelegationActive(handle);
481             maybeFailTest(transactionResult);
482             return transactionResult.getBoolResult();
483         } catch (RemoteException e) {
484             handleRemoteException(e, "isForegroundServiceDelegationActive");
485         }
486         return false;
487     }
488 }
489