1 /*
2  * Copyright (C) 2022 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 package android.wearable.cts;
17 
18 import android.app.ambientcontext.AmbientContextEventRequest;
19 import android.app.ambientcontext.AmbientContextManager;
20 import android.os.ParcelFileDescriptor;
21 import android.os.PersistableBundle;
22 import android.os.SharedMemory;
23 import android.service.ambientcontext.AmbientContextDetectionResult;
24 import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
25 import android.service.wearable.WearableSensingDataRequester;
26 import android.service.wearable.WearableSensingService;
27 import android.util.Log;
28 
29 import com.google.common.collect.HashMultimap;
30 
31 import java.util.Set;
32 import java.util.concurrent.CountDownLatch;
33 import java.util.concurrent.TimeUnit;
34 import java.util.function.Consumer;
35 
36 /** An implementation of {@link WearableSensingService} for CTS testing. */
37 public class CtsWearableSensingService extends WearableSensingService {
38     private static final String TAG = "CtsWearableSensingService";
39     private static final String FAKE_APP_PACKAGE = "foo.bar.baz";
40     private static final int INITIAL_STATUS_TO_CONSUME = -1;
41 
42     private static final AmbientContextDetectionServiceStatus INITIAL_SERVICE_STATUS_TO_CONSUME =
43             new AmbientContextDetectionServiceStatus.Builder(FAKE_APP_PACKAGE).setStatusCode(
44                     AmbientContextManager.STATUS_UNKNOWN).build();
45 
46     private static CountDownLatch sRespondLatch = new CountDownLatch(1);
47     private static Consumer<Integer> sStatusConsumer;
48     private static int sStatusToConsume;
49     private static ParcelFileDescriptor sParcelFileDescriptor;
50     private static SharedMemory sSharedMemory;
51     private static PersistableBundle sData;
52 
53     private static Consumer<AmbientContextDetectionResult> sDetectionResultConsumer;
54     private static Consumer<AmbientContextDetectionServiceStatus>
55             sAmbientContextDetectionServiceStatusConsumer;
56     private static AmbientContextDetectionServiceStatus sServiceStatusToConsume;
57     private static String sPackageName = null;
58     private static final HashMultimap<Integer, WearableSensingDataRequester> sDataRequesters =
59             HashMultimap.create();
60     private static boolean sOnStopHotwordRecognitionCalled = false;
61     private static boolean sShouldCallParentAndReturn = false;
62 
63     @Override
onDataStreamProvided(ParcelFileDescriptor parcelFileDescriptor, Consumer<Integer> statusConsumer)64     public void onDataStreamProvided(ParcelFileDescriptor parcelFileDescriptor,
65             Consumer<Integer> statusConsumer) {
66         Log.w(TAG, "onDataStreamProvided");
67         sParcelFileDescriptor = parcelFileDescriptor;
68         sStatusConsumer = statusConsumer;
69         sRespondLatch.countDown();
70     }
71 
72     @Override
onDataProvided(PersistableBundle data, SharedMemory sharedMemory, Consumer<Integer> statusConsumer)73     public void onDataProvided(PersistableBundle data, SharedMemory sharedMemory,
74             Consumer<Integer> statusConsumer) {
75         Log.w(TAG, "onDataProvided");
76         sData = data;
77         sSharedMemory = sharedMemory;
78         sStatusConsumer = statusConsumer;
79         sRespondLatch.countDown();
80     }
81 
82     @Override
onDataRequestObserverRegistered( int dataType, String packageName, WearableSensingDataRequester dataRequester, Consumer<Integer> statusConsumer)83     public void onDataRequestObserverRegistered(
84             int dataType,
85             String packageName,
86             WearableSensingDataRequester dataRequester,
87             Consumer<Integer> statusConsumer) {
88         if (sShouldCallParentAndReturn) {
89             super.onDataRequestObserverRegistered(
90                     dataType, packageName, dataRequester, statusConsumer);
91             return;
92         }
93         sDataRequesters.put(dataType, dataRequester);
94         sPackageName = packageName;
95         sStatusConsumer = statusConsumer;
96         sRespondLatch.countDown();
97     }
98 
99     @Override
onDataRequestObserverUnregistered( int dataType, String packageName, WearableSensingDataRequester dataRequester, Consumer<Integer> statusConsumer)100     public void onDataRequestObserverUnregistered(
101             int dataType,
102             String packageName,
103             WearableSensingDataRequester dataRequester,
104             Consumer<Integer> statusConsumer) {
105         if (sShouldCallParentAndReturn) {
106             super.onDataRequestObserverUnregistered(
107                     dataType, packageName, dataRequester, statusConsumer);
108             return;
109         }
110         sDataRequesters.remove(dataType, dataRequester);
111         sPackageName = packageName;
112         sStatusConsumer = statusConsumer;
113         sRespondLatch.countDown();
114     }
115 
116     @Override
onStopHotwordRecognition(Consumer<Integer> statusConsumer)117     public void onStopHotwordRecognition(Consumer<Integer> statusConsumer) {
118         if (sShouldCallParentAndReturn) {
119             super.onStopHotwordRecognition(statusConsumer);
120             return;
121         }
122         Log.i(TAG, "onStopHotwordRecognition");
123         sOnStopHotwordRecognitionCalled = true;
124         sStatusConsumer = statusConsumer;
125         sRespondLatch.countDown();
126     }
127 
128     @Override
onStartDetection(AmbientContextEventRequest request, String packageName, Consumer<AmbientContextDetectionServiceStatus> statusConsumer, Consumer<AmbientContextDetectionResult> detectionResultConsumer)129     public void onStartDetection(AmbientContextEventRequest request,
130             String packageName,
131             Consumer<AmbientContextDetectionServiceStatus> statusConsumer,
132             Consumer<AmbientContextDetectionResult> detectionResultConsumer) {
133         Log.w(TAG, "onStartDetection");
134         sPackageName = packageName;
135         sDetectionResultConsumer = detectionResultConsumer;
136         sAmbientContextDetectionServiceStatusConsumer = statusConsumer;
137         sRespondLatch.countDown();
138     }
139 
140     @Override
onStopDetection(String packageName)141     public void onStopDetection(String packageName) {
142         Log.w(TAG, "onStopDetection");
143         sPackageName = packageName;
144         sRespondLatch.countDown();
145     }
146 
147     @Override
onQueryServiceStatus(Set<Integer> eventTypes, String packageName, Consumer<AmbientContextDetectionServiceStatus> consumer)148     public void onQueryServiceStatus(Set<Integer> eventTypes,
149             String packageName,
150             Consumer<AmbientContextDetectionServiceStatus> consumer) {
151         Log.w(TAG, "onQueryServiceStatus");
152         sPackageName = packageName;
153         sAmbientContextDetectionServiceStatusConsumer = consumer;
154         sRespondLatch.countDown();
155     }
156 
reset()157     public static void reset() {
158         sRespondLatch = new CountDownLatch(1);
159         sStatusConsumer = null;
160         sStatusToConsume = INITIAL_STATUS_TO_CONSUME;
161         sParcelFileDescriptor = null;
162         sSharedMemory = null;
163         sData = null;
164         sDetectionResultConsumer = null;
165         sAmbientContextDetectionServiceStatusConsumer = null;
166         sServiceStatusToConsume = null;
167         sPackageName = null;
168         sDataRequesters.clear();
169         sOnStopHotwordRecognitionCalled = false;
170         sShouldCallParentAndReturn = false;
171     }
172 
whenCallbackTriggeredRespondWithStatus(int status)173     public static void whenCallbackTriggeredRespondWithStatus(int status) {
174         Log.w(TAG, "whenCallbackTriggeredRespondWithStatus");
175         sStatusToConsume = status;
176     }
177 
whenCallbackTriggeredRespondWithServiceStatus(int status)178     public static void whenCallbackTriggeredRespondWithServiceStatus(int status) {
179         sServiceStatusToConsume = new AmbientContextDetectionServiceStatus.Builder(
180                 FAKE_APP_PACKAGE).setStatusCode(status).build();
181     }
182 
expectTimeOut()183     public static void expectTimeOut() {
184         try {
185             if (!sRespondLatch.await(3000, TimeUnit.MILLISECONDS)) {
186                 Log.e(TAG, "Timed out waiting for result, this is expected");
187             } else {
188                 throw new AssertionError("CtsWearableSensingService"
189                         + " expected timeout but did not timeout.");
190             }
191             // reset for next
192             sRespondLatch = new CountDownLatch(1);
193             sStatusToConsume = AmbientContextManager.STATUS_UNKNOWN;
194         } catch (InterruptedException e) {
195             Log.e(TAG, e.getMessage());
196             Thread.currentThread().interrupt();
197             throw new AssertionError("Got InterruptedException while waiting for serviceStatus.");
198         }
199     }
200 
awaitResultAmbientContextDetectionService()201     public static void awaitResultAmbientContextDetectionService() {
202         try {
203             if (!sRespondLatch.await(3000, TimeUnit.MILLISECONDS)) {
204                 throw new AssertionError("CtsWearableSensingService"
205                         + " timed out while expecting a call.");
206             }
207             sAmbientContextDetectionServiceStatusConsumer.accept(sServiceStatusToConsume);
208             // reset for next
209             sRespondLatch = new CountDownLatch(1);
210             sServiceStatusToConsume = INITIAL_SERVICE_STATUS_TO_CONSUME;
211         } catch (InterruptedException e) {
212             Log.e(TAG, e.getMessage());
213             Thread.currentThread().interrupt();
214             throw new AssertionError("Got InterruptedException while waiting for serviceStatus.");
215         }
216     }
217 
awaitResult()218     public static void awaitResult() {
219         try {
220             if (!sRespondLatch.await(3000, TimeUnit.MILLISECONDS)) {
221                 throw new AssertionError("CtsWearableSensingService"
222                         + " timed out while expecting a call.");
223             }
224             Log.i(TAG, "Sending status " + sStatusToConsume);
225             sStatusConsumer.accept(sStatusToConsume);
226             // reset for next
227             sRespondLatch = new CountDownLatch(1);
228             sStatusToConsume = INITIAL_STATUS_TO_CONSUME;
229         } catch (InterruptedException e) {
230             Log.e(TAG, e.getMessage());
231             Thread.currentThread().interrupt();
232             throw new AssertionError("Got InterruptedException while waiting for serviceStatus.");
233         }
234     }
235 
236 
getData()237     public static PersistableBundle getData() {
238         return sData;
239     }
240 
getParcelFileDescriptor()241     public static ParcelFileDescriptor getParcelFileDescriptor() {
242         return sParcelFileDescriptor;
243     }
244 
getLastCallingPackage()245     public static String getLastCallingPackage() {
246         return sPackageName;
247     }
248 
249     /** Gets the data requesters registered for the provided data type. */
getDataRequesters(int dataType)250     public static Set<WearableSensingDataRequester> getDataRequesters(int dataType) {
251         return sDataRequesters.get(dataType);
252     }
253 
getOnStopHotwordRecognitionCalled()254     public static boolean getOnStopHotwordRecognitionCalled() {
255         return sOnStopHotwordRecognitionCalled;
256     }
257 
258     /**
259      * Configures this WearableSensingService to delegate to its parent when a non-abstract method
260      * is called.
261      */
configureMethodsToCallParentAndReturn()262     public static void configureMethodsToCallParentAndReturn() {
263         sShouldCallParentAndReturn = true;
264     }
265 }
266