xref: /aosp_15_r20/cts/tests/tests/car/src/android/car/cts/CarOccupantConnectionManagerTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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.car.cts;
18 
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import static org.junit.Assume.assumeNotNull;
22 
23 import android.car.Car;
24 import android.car.CarOccupantZoneManager;
25 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
26 import android.car.occupantconnection.AbstractReceiverService;
27 import android.car.occupantconnection.CarOccupantConnectionManager;
28 import android.car.occupantconnection.CarOccupantConnectionManager.ConnectionRequestCallback;
29 import android.car.occupantconnection.Payload;
30 import android.car.test.PermissionsCheckerRule.EnsureHasPermission;
31 import android.car.test.mocks.JavaMockitoHelper;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.ServiceConnection;
36 import android.os.Binder;
37 import android.os.IBinder;
38 import android.platform.test.annotations.AppModeFull;
39 import android.util.Log;
40 import android.util.Pair;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 import androidx.test.runner.AndroidJUnit4;
45 
46 import com.android.compatibility.common.util.ApiTest;
47 import com.android.compatibility.common.util.PollingCheck;
48 
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 
53 import java.util.ArrayList;
54 import java.util.HexFormat;
55 import java.util.List;
56 import java.util.concurrent.CountDownLatch;
57 import java.util.concurrent.Executor;
58 
59 @RunWith(AndroidJUnit4.class)
60 @AppModeFull(reason = "Test relies on other server to connect to.")
61 @EnsureHasPermission({Car.PERMISSION_MANAGE_REMOTE_DEVICE,
62         Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION})
63 public final class CarOccupantConnectionManagerTest extends AbstractCarTestCase {
64 
65     private static final String TAG = CarOccupantConnectionManagerTest.class.getSimpleName();
66     private static final String RECEIVER_ID = "test_receiver_endpoint_id";
67 
68     private static final long BINDING_TIMEOUT_MS = 3_000;
69     private static final long WAIT_BEFORE_RESPOND_TO_REQUEST_MS = 2_000;
70     private static final long CALLBACK_TIMEOUT_MS = WAIT_BEFORE_RESPOND_TO_REQUEST_MS + 2_000;
71     private static final long EXCHANGE_PAYLOAD_TIMEOUT_MS = 10_000;
72     private static final long POLLING_CHECK_TIMEOUT_MS = 3_000;
73 
74     private static final Payload PAYLOAD1 = new Payload(HexFormat.of().parseHex("1234"));
75     private static final Payload PAYLOAD2 = new Payload(HexFormat.of().parseHex("5678"));
76 
77     private final Executor mExecutor = mContext.getMainExecutor();
78     private final TestServiceConnection mServiceConnection = new TestServiceConnection();
79 
80     private CarOccupantConnectionManager mOccupantConnectionManager;
81 
82     private TestReceiverService.LocalBinder mBinder;
83     private OccupantZoneInfo mActivePeerZone;
84 
85     @Before
setUp()86     public void setUp() {
87         mOccupantConnectionManager = getCar().getCarManager(CarOccupantConnectionManager.class);
88         // CarOccupantConnectionManager is available on multi-display builds only.
89         // TODO(b/265091454): annotate the test with @RequireMultipleUsersOnMultipleDisplays.
90         assumeNotNull("Skip the test because CarOccupantConnectionManager is not available on"
91                 + " this build", mOccupantConnectionManager);
92 
93         CarOccupantZoneManager occupantZoneManager =
94                 getCar().getCarManager(CarOccupantZoneManager.class);
95         // Make the sender app request a connection and send Payloads to itself. See b/302567579.
96         mActivePeerZone = occupantZoneManager.getMyOccupantZone();
97     }
98 
99     @Test
100     @ApiTest(apis = {
101             "android.car.occupantconnection.CarOccupantConnectionManager#registerReceiver",
102             "android.car.occupantconnection.CarOccupantConnectionManager#unregisterReceiver",
103             "android.car.occupantconnection.AbstractReceiverService#getAllReceiverEndpoints",
104             "android.car.occupantconnection.AbstractReceiverService#onLocalServiceBind"})
testRegisterAndUnregisterReceiver()105     public void testRegisterAndUnregisterReceiver() throws Exception {
106         mOccupantConnectionManager.registerReceiver(RECEIVER_ID, mExecutor,
107                 (senderZone, payload) -> {
108                 });
109         TestReceiverService receiverService = bindToLocalReceiverServiceAndWait();
110 
111         PollingCheck.check("Failed to register the receiver", POLLING_CHECK_TIMEOUT_MS,
112                 () -> receiverService.getAllReceiverEndpoints().contains(RECEIVER_ID));
113 
114         mOccupantConnectionManager.unregisterReceiver(RECEIVER_ID);
115 
116         PollingCheck.check("Failed to unregister the receiver", POLLING_CHECK_TIMEOUT_MS,
117                 () -> receiverService.getAllReceiverEndpoints().isEmpty());
118     }
119 
120     @Test
121     @ApiTest(apis = {
122             "android.car.occupantconnection.CarOccupantConnectionManager#requestConnection",
123             "android.car.occupantconnection.CarOccupantConnectionManager#cancelConnection"})
testRequestAndCancelConnection()124     public void testRequestAndCancelConnection() {
125         ConnectionRequestCallback connectionRequestCallback = new ConnectionRequestCallback() {
126             @Override
127             public void onConnected(@NonNull OccupantZoneInfo receiverZone) {
128             }
129 
130             @Override
131             public void onFailed(@NonNull OccupantZoneInfo receiverZone,
132                     int connectionError) {
133             }
134 
135             @Override
136             public void onDisconnected(@NonNull OccupantZoneInfo receiverZone) {
137             }
138         };
139 
140         mOccupantConnectionManager.requestConnection(mActivePeerZone, mExecutor,
141                 connectionRequestCallback);
142         // No exception should be thrown.
143         mOccupantConnectionManager.cancelConnection(mActivePeerZone);
144 
145         mOccupantConnectionManager.requestConnection(mActivePeerZone, mExecutor,
146                 connectionRequestCallback);
147         mOccupantConnectionManager.cancelConnection(mActivePeerZone);
148     }
149 
150     /**
151      * Test:
152      * <ul>
153      *   <li> The sender requests a connection to the receiver. Then the receiver rejects it.
154      *   <li> The sender requests another connection to the receiver. Then the receiver accepts it.
155      *   <li> The sender sends PAYLOAD1 to the receiver. Then the receiver verifies PAYLOAD1,
156      *        requests two connections to the sender (the first request will be rejected, while
157      *        the second one will be accepted), and sends PAYLOAD2 to the sender. Then the sender
158      *        verifies PAYLOAD2.
159      *   <li> The sender disconnects.
160      * </ul>
161      */
162     @Test
163     @ApiTest(apis = {
164             "android.car.occupantconnection.CarOccupantConnectionManager#requestConnection",
165             "android.car.occupantconnection.CarOccupantConnectionManager#isConnected",
166             "android.car.occupantconnection.CarOccupantConnectionManager#sendPayload",
167             "android.car.occupantconnection.CarOccupantConnectionManager#disconnect",
168             "android.car.occupantconnection.AbstractReceiverService#acceptConnection",
169             "android.car.occupantconnection.AbstractReceiverService#rejectConnection",
170             "android.car.occupantconnection.AbstractReceiverService#onPayloadReceived"})
testConnectAndSendPayload()171     public void testConnectAndSendPayload()
172             throws CarOccupantConnectionManager.PayloadTransferException {
173         TestReceiverService receiverService = bindToLocalReceiverServiceAndWait();
174 
175         boolean[] onConnectedInvoked = new boolean[1];
176         boolean[] onFailedInvoked = new boolean[1];
177         int[] connectionErrors = new int[1];
178         ConnectionRequestCallback connectionRequestCallback = new ConnectionRequestCallback() {
179             @Override
180             public void onConnected(@NonNull OccupantZoneInfo receiverZone) {
181                 onConnectedInvoked[0] = true;
182             }
183 
184             @Override
185             public void onFailed(@NonNull OccupantZoneInfo receiverZone,
186                     int connectionError) {
187                 onFailedInvoked[0] = true;
188                 connectionErrors[0] = connectionError;
189             }
190 
191             @Override
192             public void onDisconnected(@NonNull OccupantZoneInfo receiverZone) {
193             }
194         };
195 
196         // The receiver service will reject the first request.
197         Log.v(TAG, "Sender requests a connection for the first time");
198         mOccupantConnectionManager.requestConnection(mActivePeerZone, mExecutor,
199                 connectionRequestCallback);
200         PollingCheck.waitFor(CALLBACK_TIMEOUT_MS,
201                 () -> !onConnectedInvoked[0] && onFailedInvoked[0]
202                         && connectionErrors[0] == TestReceiverService.REJECTION_REASON);
203         Log.v(TAG, "Sender's first request is rejected");
204 
205         // The receiver service will accept the second request.
206         Log.v(TAG, "Sender requests another connection");
207         onConnectedInvoked[0] = false;
208         onFailedInvoked[0] = false;
209         mOccupantConnectionManager.requestConnection(mActivePeerZone, mExecutor,
210                 connectionRequestCallback);
211         PollingCheck.waitFor(CALLBACK_TIMEOUT_MS,
212                 () -> onConnectedInvoked[0] && !onFailedInvoked[0]);
213         Log.v(TAG, "Sender's second request is accepted");
214 
215         assertWithMessage("It should be connected to %s", mActivePeerZone)
216                 .that(mOccupantConnectionManager.isConnected(mActivePeerZone)).isTrue();
217 
218         Log.v(TAG, "Sender sends PAYLOAD1 to the receiver");
219         mOccupantConnectionManager.sendPayload(mActivePeerZone, PAYLOAD1);
220         Pair<OccupantZoneInfo, Payload> expectedResponse = new Pair<>(mActivePeerZone, PAYLOAD2);
221         PollingCheck.waitFor(EXCHANGE_PAYLOAD_TIMEOUT_MS,
222                 () -> receiverService.mOnPayloadReceivedInvokedRecords.contains(expectedResponse));
223         Log.v(TAG, "Sender receives PAYLOAD2 from the receiver");
224 
225         mOccupantConnectionManager.disconnect(mActivePeerZone);
226         assertWithMessage("Sender should be disconnected to %s", mActivePeerZone)
227                 .that(mOccupantConnectionManager.isConnected(mActivePeerZone)).isFalse();
228     }
229 
bindToLocalReceiverServiceAndWait()230     private TestReceiverService bindToLocalReceiverServiceAndWait() {
231         Log.v(TAG, "Binding to local receiver service");
232         Intent intent = new Intent();
233         intent.setClassName(mContext, TestReceiverService.class.getName());
234         mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
235         try {
236             JavaMockitoHelper.await(mServiceConnection.latch, BINDING_TIMEOUT_MS);
237         } catch (InterruptedException e) {
238             throw new RuntimeException(e);
239         }
240         Log.v(TAG, "Local receiver service bounded");
241         return mBinder.getService();
242     }
243 
244     private final class TestServiceConnection implements ServiceConnection {
245 
246         public final CountDownLatch latch = new CountDownLatch(1);
247 
248         @Override
onServiceConnected(ComponentName name, IBinder service)249         public void onServiceConnected(ComponentName name, IBinder service) {
250             mBinder = (TestReceiverService.LocalBinder) service;
251             latch.countDown();
252         }
253 
254         @Override
onServiceDisconnected(ComponentName name)255         public void onServiceDisconnected(ComponentName name) {
256         }
257     }
258 
259     /**
260      * An implementation of AbstractReceiverService.
261      * <p>
262      * This service will wait for a while before responding to a connection request (to allow the
263      * sender to cancel the request). After that, it will reject the first request, and accept the
264      * second request.
265      * <p>
266      * When this service receives PAYLOAD1 from the sender, it will send two connection requests to
267      * the sender's receiver service. The first request wil be rejected, while the second request
268      * will be accepted. Once it is accepted, it will send PAYLOAD2 to the sender.
269      */
270     public static class TestReceiverService extends AbstractReceiverService {
271 
272         private static final int REJECTION_REASON = 123;
273 
274         // The following lists are used to verify an onFoo() method was invoked with certain
275         // parameters.
276         private final List<Pair<OccupantZoneInfo, Payload>> mOnPayloadReceivedInvokedRecords =
277                 new ArrayList<>();
278 
279         private final LocalBinder mLocalBinder = new LocalBinder();
280 
281         private Car mCar;
282         private CarOccupantConnectionManager mOccupantConnectionManager;
283         private Object mOnConnectionInitiatedLock = new Object();
284         private boolean mRejected = false;
285 
286         private final Car.CarServiceLifecycleListener mCarServiceLifecycleListener =
287                 (car, ready) -> {
288                     if (!ready) {
289                         mOccupantConnectionManager = null;
290                         return;
291                     }
292                     mCar = car;
293                     mOccupantConnectionManager = car.getCarManager(
294                             CarOccupantConnectionManager.class);
295                 };
296 
297         private class LocalBinder extends Binder {
298 
getService()299             TestReceiverService getService() {
300                 return TestReceiverService.this;
301             }
302         }
303 
304         @Override
onPayloadReceived(OccupantZoneInfo senderZone, Payload payload)305         public void onPayloadReceived(OccupantZoneInfo senderZone, Payload payload) {
306             mOnPayloadReceivedInvokedRecords.add(new Pair(senderZone, payload));
307             PollingCheck.waitFor(CALLBACK_TIMEOUT_MS, () -> mOccupantConnectionManager != null);
308 
309             if (PAYLOAD1.equals(payload)) {
310                 try {
311                     Log.v(TAG, "The receiver service just sends PAYLOAD2 to the sender"
312                             + " without establishing a new connection since they run in the"
313                             + " same occupant zone");
314                     mOccupantConnectionManager.sendPayload(senderZone, PAYLOAD2);
315                 } catch (CarOccupantConnectionManager.PayloadTransferException e) {
316                     throw new RuntimeException(e);
317                 }
318             }
319         }
320 
321         @Override
onReceiverRegistered(String receiverEndpointId)322         public void onReceiverRegistered(String receiverEndpointId) {
323             Log.v(TAG, "onReceiverRegistered:" + receiverEndpointId);
324         }
325 
326         @Override
onConnectionInitiated(OccupantZoneInfo senderZone)327         public void onConnectionInitiated(OccupantZoneInfo senderZone) {
328             // Wait a while to allow some time for the sender to cancel the request.
329             try {
330                 Thread.sleep(WAIT_BEFORE_RESPOND_TO_REQUEST_MS);
331             } catch (InterruptedException e) {
332                 throw new RuntimeException(e);
333             }
334 
335             synchronized (mOnConnectionInitiatedLock) {
336                 // If the sender didn't cancel the request, reject the first request, and accept
337                 // the second request.
338                 if (!mRejected) {
339                     rejectConnection(senderZone, REJECTION_REASON);
340                     mRejected = true;
341                 } else {
342                     acceptConnection(senderZone);
343                 }
344             }
345         }
346 
347         @Override
onConnected(OccupantZoneInfo senderZone)348         public void onConnected(OccupantZoneInfo senderZone) {
349         }
350 
351         @Override
onConnectionCanceled(OccupantZoneInfo senderZone)352         public void onConnectionCanceled(OccupantZoneInfo senderZone) {
353         }
354 
355         @Override
onDisconnected(OccupantZoneInfo senderZone)356         public void onDisconnected(OccupantZoneInfo senderZone) {
357         }
358 
359         @Override
onCreate()360         public void onCreate() {
361             super.onCreate();
362             mCar = Car.createCar(this, /* handler= */ null,
363                     Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mCarServiceLifecycleListener);
364         }
365 
366         @Override
onDestroy()367         public void onDestroy() {
368             if (mCar != null && mCar.isConnected()) {
369                 mCar.disconnect();
370                 mCar = null;
371             }
372             super.onDestroy();
373         }
374 
375         @Nullable
376         @Override
onLocalServiceBind(@onNull Intent intent)377         public IBinder onLocalServiceBind(@NonNull Intent intent) {
378             return mLocalBinder;
379         }
380     }
381 }
382