• 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 org.junit.Assert.fail;
20 
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.os.IBinder;
26 import android.os.SystemClock;
27 import android.util.Log;
28 import android.util.Pair;
29 
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.concurrent.CompletableFuture;
33 import java.util.concurrent.TimeUnit;
34 
35 public class BindUtils {
36     private static final String TAG = BindUtils.class.getSimpleName();
37     private static final Map<TelecomTestApp, Pair<TelecomAppServiceConnection, AppControlWrapper>>
38             sTelecomAppToService = new HashMap<>();
39 
hasBoundTestApp()40     public static boolean hasBoundTestApp() {
41         // If the sTelecomAppToService is NOT empty, that means that an app has not unbound yet
42         return !sTelecomAppToService.isEmpty();
43     }
44 
printBoundTestApps()45     public static void printBoundTestApps() {
46         for (TelecomTestApp testAppName : sTelecomAppToService.keySet()) {
47             Log.i(TAG, String.format("printBoundTestApps: [%s] is currently bound", testAppName));
48 
49         }
50     }
51 
52     private static class TelecomAppServiceConnection implements ServiceConnection {
53         private static final String TAG = TelecomAppServiceConnection.class.getSimpleName();
54         private final CompletableFuture<IAppControl> mFuture;
55 
TelecomAppServiceConnection(CompletableFuture<IAppControl> future)56         TelecomAppServiceConnection(CompletableFuture<IAppControl> future) {
57             mFuture = future;
58         }
59 
60         @Override
onServiceConnected(ComponentName name, IBinder service)61         public void onServiceConnected(ComponentName name, IBinder service) {
62             Log.i(TAG, String.format("onServiceConnected: ComponentName=[%s]", name));
63             mFuture.complete(IAppControl.Stub.asInterface(service));
64         }
65 
66         @Override
onServiceDisconnected(ComponentName name)67         public void onServiceDisconnected(ComponentName name) {
68             Log.i(TAG, String.format("onServiceDisconnected: ComponentName=[%s]", name));
69             mFuture.complete(null);
70         }
71     }
72 
73     /**
74      * This helper method handles the binding process for a given test app.  It ensures the
75      * that the test app signals that onBind is finished executing before returning control to the
76      * test.
77      */
bindToApp(Context context, TelecomTestApp appName)78     public AppControlWrapper bindToApp(Context context, TelecomTestApp appName)
79             throws Exception {
80         // if a test app is already bound, return the existing binding
81         if (sTelecomAppToService.containsKey(appName)) {
82             return sTelecomAppToService.get(appName).second;
83         }
84         final AppControlWrapper appControl = waitOnBindForApp(context, appName);
85         // For debugging purposes, it is good to know how much time for an app to signal it bound!
86         Log.i(TAG, String.format("bindToApp: wait for %s to signal onBind is complete ", appName));
87         long startTimeMillis = SystemClock.elapsedRealtime();
88         WaitUntil.waitUntilConditionIsTrueOrTimeout(new Condition() {
89                     @Override
90                     public Object expected() {
91                         return true;
92                     }
93 
94                     @Override
95                     public Object actual() {
96                         return appControl.isBound();
97                     }
98                 },
99                 WaitUntil.DEFAULT_TIMEOUT_MS,
100                 "Timed out waiting for isBound to return <TRUE> for [" + appName + "]");
101         long elapsed = SystemClock.elapsedRealtime() - startTimeMillis;
102         Log.i(TAG, String.format("bindToApp: %s took %d milliseconds signal it was bound",
103                 appName, elapsed));
104         return appControl;
105     }
106 
107     /**
108      * This helper method handles the unbinding process for a given test app.  It ensures the
109      * that the test app signals that onUnbind is finished executing before returning control to the
110      * test.
111      */
unbindFromApp(Context context, AppControlWrapper appControl)112     public void unbindFromApp(Context context, AppControlWrapper appControl) {
113         TelecomTestApp name = appControl.getTelecomApps();
114         Log.i(TAG, String.format("unbindFromApplication: applicationName=[%s]", name));
115         if (!sTelecomAppToService.containsKey(name)) {
116             Log.i(TAG, String.format("cannot find the service binder for application=[%s]", name));
117             return;
118         }
119         try {
120             TelecomAppServiceConnection serviceConnection = sTelecomAppToService.get(name).first;
121             context.unbindService(serviceConnection);
122             long startTimeMillis = SystemClock.elapsedRealtime();
123             Log.i(TAG, String.format("unbindFromApp: wait for %s to signal onUnbind is complete ",
124                     name));
125             WaitUntil.waitUntilConditionIsTrueOrTimeout(new Condition() {
126                         @Override
127                         public Object expected() {
128                             return false;
129                         }
130 
131                         @Override
132                         public Object actual() {
133                             return appControl.isBound();
134                         }
135                     },
136                     WaitUntil.DEFAULT_TIMEOUT_MS,
137                     "Timed out waiting for isBound to return <FALSE>");
138             long elapsedMs = SystemClock.elapsedRealtime() - startTimeMillis;
139             Log.i(TAG, String.format("unbindFromApp: %s took %d milliseconds to signal it was "
140                     + "unbound", name, elapsedMs));
141         } catch (Exception e) {
142             // Note: Do not throw the UnBind Exception! Otherwise, the test will fail with an unbind
143             // error instead of a potential underlying cause.
144             Log.e(TAG, String.format("unbindFromApplication: app=[%s], e=[%s]", name, e));
145         }
146         finally {
147             sTelecomAppToService.remove(name);
148         }
149     }
150 
createBindIntentForApplication(TelecomTestApp application)151     private Intent createBindIntentForApplication(TelecomTestApp application) throws Exception {
152         Intent bindIntent = new Intent(getBindActionFromApplicationName(application));
153         bindIntent.setPackage(getPackageNameFromApplicationName(application));
154         return bindIntent;
155     }
156 
getBindActionFromApplicationName(TelecomTestApp app)157     private String getBindActionFromApplicationName(TelecomTestApp app) throws Exception {
158         switch (app) {
159             case TransactionalVoipAppMain, TransactionalVoipAppClone -> {
160                 return TelecomTestApp.T_CONTROL_INTERFACE_ACTION;
161             }
162             case ConnectionServiceVoipAppMain, ConnectionServiceVoipAppClone -> {
163                 return TelecomTestApp.VOIP_CS_CONTROL_INTERFACE_ACTION;
164             }
165             case ManagedConnectionServiceApp, ManagedConnectionServiceAppClone -> {
166                 return TelecomTestApp.CONTROL_INTERFACE_ACTION;
167             }
168         }
169         throw new Exception(
170                 String.format("%s doesn't have a <CONTROL_INTERFACE> mapping." + app));
171     }
172 
getPackageNameFromApplicationName(TelecomTestApp app)173     private static String getPackageNameFromApplicationName(TelecomTestApp app) throws Exception {
174         switch (app) {
175             case TransactionalVoipAppMain -> {
176                 return TelecomTestApp.TRANSACTIONAL_PACKAGE_NAME;
177             }
178             case TransactionalVoipAppClone -> {
179                 return TelecomTestApp.TRANSACTIONAL_CLONE_PACKAGE_NAME;
180             }
181             case ConnectionServiceVoipAppMain -> {
182                 return TelecomTestApp.SELF_MANAGED_CS_MAIN_PACKAGE_NAME;
183             }
184             case ConnectionServiceVoipAppClone -> {
185                 return TelecomTestApp.SELF_MANAGED_CS_CLONE_PACKAGE_NAME;
186             }
187             case ManagedConnectionServiceApp -> {
188                 return TelecomTestApp.MANAGED_PACKAGE_NAME;
189             }
190             case ManagedConnectionServiceAppClone -> {
191                 return TelecomTestApp.MANAGED_CLONE_PACKAGE_NAME;
192             }
193         }
194         throw new Exception(
195                 String.format("%s doesn't have a <PACKAGE_NAME> mapping.", app));
196     }
197 
waitOnBindForApp(Context context, TelecomTestApp appName)198     private AppControlWrapper waitOnBindForApp(Context context, TelecomTestApp appName)
199             throws Exception {
200         TelecomAppServiceConnection serviceConnection;
201         if (sTelecomAppToService.containsKey(appName)) {
202             Log.i(TAG, String.format("returning cached service binder for application=[%s]",
203                     appName));
204             return sTelecomAppToService.get(appName).second;
205         } else {
206             CompletableFuture<IAppControl> f = new CompletableFuture<>();
207             serviceConnection = new TelecomAppServiceConnection(f);
208             Log.i(TAG, String.format("waitOnBindForApp: requesting bind to %s", appName));
209             long startTimeMillis = SystemClock.elapsedRealtime();
210             boolean success = context.bindService(createBindIntentForApplication(appName),
211                     serviceConnection, Context.BIND_AUTO_CREATE);
212             long elapsedMs = SystemClock.elapsedRealtime() - startTimeMillis;
213             Log.i(TAG, String.format("waitOnBindForApp: finished bind to %s in %d milliseconds",
214                     appName, elapsedMs));
215             if (!success) {
216                 fail("Failed to get control interface -- bind error");
217             }
218             IAppControl iAppControl = f.get(WaitUntil.DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
219             AppControlWrapper wrapper = new AppControlWrapper(iAppControl, appName);
220             sTelecomAppToService.put(appName, new Pair<>(serviceConnection, wrapper));
221             return wrapper;
222         }
223     }
224 }
225