1 /*
2  * Copyright (C) 2020 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.controls.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.mockito.Mockito.anyInt;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.when;
26 
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.res.Resources;
31 import android.service.controls.Control;
32 import android.service.controls.ControlsProviderService;
33 import android.service.controls.actions.BooleanAction;
34 import android.service.controls.actions.CommandAction;
35 import android.service.controls.actions.ControlAction;
36 import android.service.controls.actions.FloatAction;
37 import android.service.controls.actions.ModeAction;
38 import android.service.controls.templates.ControlTemplate;
39 import android.service.controls.templates.RangeTemplate;
40 import android.service.controls.templates.TemperatureControlTemplate;
41 import android.service.controls.templates.ToggleRangeTemplate;
42 import android.service.controls.templates.ToggleTemplate;
43 import android.test.mock.MockContext;
44 
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.concurrent.Flow.Publisher;
50 import java.util.concurrent.Flow.Subscriber;
51 import java.util.concurrent.Flow.Subscription;
52 import java.util.function.Consumer;
53 
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 
58 @RunWith(AndroidJUnit4.class)
59 public class CtsControlsServiceTest {
60 
61     private static final String ACTION_ADD_CONTROL = "android.service.controls.action.ADD_CONTROL";
62     private static final String EXTRA_CONTROL = "android.service.controls.extra.CONTROL";
63 
64     private CtsControlsService mControlsService;
65 
66     @Before
setUp()67     public void setUp() {
68         mControlsService = new CtsControlsService();
69     }
70 
71     @Test
testLoadAllAvailable()72     public void testLoadAllAvailable() {
73         Publisher<Control> publisher = mControlsService.createPublisherForAllAvailable();
74         List<Control> loadedControls = new ArrayList<>();
75         subscribe(publisher, 10, loadedControls);
76 
77         List<Control> expectedControls = new ArrayList<>();
78         expectedControls.add(new Control.StatelessBuilder(
79                 mControlsService.buildLight(false, 0.0f)).build());
80         expectedControls.add(new Control.StatelessBuilder(
81                 mControlsService.buildLock(false)).build());
82         expectedControls.add(new Control.StatelessBuilder(
83                 mControlsService.buildRoutine()).build());
84         expectedControls.add(new Control.StatelessBuilder(mControlsService.buildThermostat(
85                 TemperatureControlTemplate.MODE_OFF)).build());
86         expectedControls.add(new Control.StatelessBuilder(
87                 mControlsService.buildMower(false)).build());
88         expectedControls.add(new Control.StatelessBuilder(
89                 mControlsService.buildSwitch(false)).build());
90         expectedControls.add(new Control.StatelessBuilder(
91                 mControlsService.buildGate(false)).build());
92         expectedControls.add(new Control.StatelessBuilder(
93                 mControlsService.buildCamera(true)).build());
94 
95         assertControlsList(loadedControls, expectedControls);
96     }
97 
98     @Test
testLoadSuggested()99     public void testLoadSuggested() {
100         Publisher<Control> publisher = mControlsService.createPublisherForSuggested();
101         List<Control> loadedControls = new ArrayList<>();
102         subscribe(publisher, 3, loadedControls);
103 
104         List<Control> expectedControls = new ArrayList<>();
105         expectedControls.add(new Control.StatelessBuilder(
106                 mControlsService.buildLight(false, 0.0f)).build());
107         expectedControls.add(new Control.StatelessBuilder(
108                 mControlsService.buildLock(false)).build());
109         expectedControls.add(new Control.StatelessBuilder(
110                 mControlsService.buildRoutine()).build());
111 
112         assertControlsList(loadedControls, expectedControls);
113     }
114 
115     @Test
testPublisherForSingleControl()116     public void testPublisherForSingleControl() {
117         List<String> idsToLoad = new ArrayList<>();
118         idsToLoad.add("mower");
119 
120         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
121         List<Control> loadedControls = new ArrayList<>();
122         subscribe(publisher, 10, loadedControls);
123 
124         List<Control> expectedControls = new ArrayList<>();
125         expectedControls.add(mControlsService.buildMower(false));
126 
127         assertControlsList(loadedControls, expectedControls);
128     }
129 
130     @Test
testPublisherForMultipleControls()131     public void testPublisherForMultipleControls() {
132         List<String> idsToLoad = new ArrayList<>();
133         idsToLoad.add("lock");
134         idsToLoad.add("light");
135 
136         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
137         List<Control> loadedControls = new ArrayList<>();
138         subscribe(publisher, 10, loadedControls);
139 
140         List<Control> expectedControls = new ArrayList<>();
141         expectedControls.add(mControlsService.buildLock(false));
142         expectedControls.add(mControlsService.buildLight(false, 0.0f));
143 
144         assertControlsList(loadedControls, expectedControls);
145     }
146 
147     @Test
testBooleanAction()148     public void testBooleanAction() {
149         List<String> idsToLoad = new ArrayList<>();
150         idsToLoad.add("switch");
151 
152         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
153         List<Control> loadedControls = new ArrayList<>();
154         subscribe(publisher, 10, loadedControls);
155 
156         BooleanAction action = new BooleanAction("action", true);
157         assertEquals(action.getActionType(), ControlAction.TYPE_BOOLEAN);
158         mControlsService.performControlAction("switch", action,
159                 assertConsumer(ControlAction.RESPONSE_OK));
160 
161         List<Control> expectedControls = new ArrayList<>();
162         expectedControls.add(mControlsService.buildSwitch(false));
163         expectedControls.add(mControlsService.buildSwitch(true));
164 
165         assertControlsList(loadedControls, expectedControls);
166     }
167 
168     @Test
testFloatAction()169     public void testFloatAction() {
170         List<String> idsToLoad = new ArrayList<>();
171         idsToLoad.add("light");
172 
173         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
174         List<Control> loadedControls = new ArrayList<>();
175         subscribe(publisher, 10, loadedControls);
176 
177         mControlsService.performControlAction("light", new BooleanAction("action", true),
178                 assertConsumer(ControlAction.RESPONSE_OK));
179 
180         FloatAction action = new FloatAction("action", 80.0f);
181         assertEquals(action.getActionType(), ControlAction.TYPE_FLOAT);
182         mControlsService.performControlAction("light", action,
183                 assertConsumer(ControlAction.RESPONSE_OK));
184 
185         List<Control> expectedControls = new ArrayList<>();
186         expectedControls.add(mControlsService.buildLight(false, 0.0f));
187         expectedControls.add(mControlsService.buildLight(true, 50.0f));
188         expectedControls.add(mControlsService.buildLight(true, 80.0f));
189 
190         assertControlsList(loadedControls, expectedControls);
191     }
192 
193     @Test
testCommandAction()194     public void testCommandAction() {
195         List<String> idsToLoad = new ArrayList<>();
196         idsToLoad.add("routine");
197 
198         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
199         List<Control> loadedControls = new ArrayList<>();
200         subscribe(publisher, 10, loadedControls);
201 
202         CommandAction action = new CommandAction("action");
203         assertEquals(action.getActionType(), ControlAction.TYPE_COMMAND);
204         mControlsService.performControlAction("routine", action,
205                 assertConsumer(ControlAction.RESPONSE_OK));
206 
207         List<Control> expectedControls = new ArrayList<>();
208         expectedControls.add(mControlsService.buildRoutine());
209         expectedControls.add(mControlsService.buildRoutine());
210 
211         assertControlsList(loadedControls, expectedControls);
212     }
213 
214     @Test
testBooleanActionWithPinChallenge()215     public void testBooleanActionWithPinChallenge() {
216         List<String> idsToLoad = new ArrayList<>();
217         idsToLoad.add("lock");
218 
219         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
220         List<Control> loadedControls = new ArrayList<>();
221         subscribe(publisher, 10, loadedControls);
222 
223         mControlsService.performControlAction("lock", new BooleanAction("action", true),
224                 assertConsumer(ControlAction.RESPONSE_CHALLENGE_PIN));
225 
226         mControlsService.performControlAction("lock", new BooleanAction("action", true, "1234"),
227                 assertConsumer(ControlAction.RESPONSE_OK));
228 
229         List<Control> expectedControls = new ArrayList<>();
230         expectedControls.add(mControlsService.buildLock(false));
231         expectedControls.add(mControlsService.buildLock(true));
232 
233         assertControlsList(loadedControls, expectedControls);
234     }
235 
236     @Test
testBooleanActionWithPassphraseChallenge()237     public void testBooleanActionWithPassphraseChallenge() {
238         List<String> idsToLoad = new ArrayList<>();
239         idsToLoad.add("gate");
240 
241         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
242         List<Control> loadedControls = new ArrayList<>();
243         subscribe(publisher, 10, loadedControls);
244 
245         mControlsService.performControlAction("gate", new BooleanAction("action", true),
246                 assertConsumer(ControlAction.RESPONSE_CHALLENGE_PASSPHRASE));
247 
248         mControlsService.performControlAction("gate", new BooleanAction("action", true, "abc123"),
249                 assertConsumer(ControlAction.RESPONSE_OK));
250 
251         List<Control> expectedControls = new ArrayList<>();
252         expectedControls.add(mControlsService.buildGate(false));
253         expectedControls.add(mControlsService.buildGate(true));
254 
255         assertControlsList(loadedControls, expectedControls);
256     }
257 
258     @Test
testBooleanActionWithAckChallenge()259     public void testBooleanActionWithAckChallenge() {
260         List<String> idsToLoad = new ArrayList<>();
261         idsToLoad.add("mower");
262 
263         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
264         List<Control> loadedControls = new ArrayList<>();
265         subscribe(publisher, 10, loadedControls);
266 
267         mControlsService.performControlAction("mower", new BooleanAction("action", true),
268                 assertConsumer(ControlAction.RESPONSE_CHALLENGE_ACK));
269 
270         mControlsService.performControlAction("mower", new BooleanAction("action", true, "true"),
271                 assertConsumer(ControlAction.RESPONSE_OK));
272 
273         List<Control> expectedControls = new ArrayList<>();
274         expectedControls.add(mControlsService.buildMower(false));
275         expectedControls.add(mControlsService.buildMower(true));
276 
277         assertControlsList(loadedControls, expectedControls);
278     }
279 
280     @Test
testModeAction()281     public void testModeAction() {
282         List<String> idsToLoad = new ArrayList<>();
283         idsToLoad.add("thermostat");
284 
285         Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
286         List<Control> loadedControls = new ArrayList<>();
287         subscribe(publisher, 10, loadedControls);
288 
289         ModeAction action = new ModeAction("action", TemperatureControlTemplate.MODE_COOL);
290         assertEquals(action.getActionType(), ControlAction.TYPE_MODE);
291         mControlsService.performControlAction("thermostat", action,
292                 assertConsumer(ControlAction.RESPONSE_OK));
293 
294         List<Control> expectedControls = new ArrayList<>();
295         expectedControls.add(mControlsService.buildThermostat(TemperatureControlTemplate.MODE_OFF));
296         expectedControls.add(mControlsService.buildThermostat(
297                 TemperatureControlTemplate.MODE_COOL));
298 
299         assertControlsList(loadedControls, expectedControls);
300     }
301 
302     @Test
testRequestAddControl()303     public void testRequestAddControl() {
304         Resources res = mock(Resources.class);
305         when(res.getString(anyInt())).thenReturn("");
306 
307         final ComponentName testComponent = new ComponentName("TestPkg", "TestClass");
308         final Control control = new Control.StatelessBuilder(mControlsService.buildMower(false))
309                 .build();
310 
311         Context context = new MockContext() {
312             public Resources getResources() {
313                 return res;
314             }
315 
316             public void sendBroadcast(Intent intent, String receiverPermission) {
317                 assertEquals(intent.getAction(), ACTION_ADD_CONTROL);
318                 assertEquals((ComponentName) intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME),
319                         testComponent);
320                 assertEquals((Control) intent.getParcelableExtra(EXTRA_CONTROL), control);
321                 assertEquals(receiverPermission, "android.permission.BIND_CONTROLS");
322             }
323         };
324 
325         ControlsProviderService.requestAddControl(context, testComponent, control);
326     }
327 
assertConsumer(int expectedStatus)328     private Consumer<Integer> assertConsumer(int expectedStatus) {
329         return (status) -> {
330             ControlAction.isValidResponse(status);
331             assertEquals((int) status, expectedStatus);
332         };
333     }
334 
subscribe(Publisher<Control> publisher, final int request, final List<Control> addToList)335     private void subscribe(Publisher<Control> publisher, final int request,
336             final List<Control> addToList) {
337         publisher.subscribe(new Subscriber<Control>() {
338                 public void onSubscribe(Subscription s) {
339                     s.request(request);
340                 }
341 
342                 public void onNext(Control c) {
343                     addToList.add(c);
344                 }
345 
346                 public void onError(Throwable t) {
347                     throw new IllegalStateException("onError should not be called here");
348                 }
349 
350                 public void onComplete() {
351 
352                 }
353             });
354     }
355 
assertControlsList(List<Control> actualControls, List<Control> expectedControls)356     private void assertControlsList(List<Control> actualControls, List<Control> expectedControls) {
357         assertEquals(actualControls.size(), expectedControls.size());
358 
359         for (int i = 0; i < actualControls.size(); i++) {
360             assertControlEquals(actualControls.get(i), expectedControls.get(i));
361         }
362     }
363 
assertControlEquals(Control c1, Control c2)364     private void assertControlEquals(Control c1, Control c2) {
365         assertEquals(c1.getTitle(), c2.getTitle());
366         assertEquals(c1.getSubtitle(), c2.getSubtitle());
367         assertEquals(c1.getStructure(), c2.getStructure());
368         assertEquals(c1.getZone(), c2.getZone());
369         assertEquals(c1.getDeviceType(), c2.getDeviceType());
370         assertEquals(c1.getStatus(), c2.getStatus());
371         assertEquals(c1.getControlId(), c2.getControlId());
372         assertEquals(c1.getCustomIcon(), c2.getCustomIcon());
373         assertEquals(c1.getCustomColor(), c2.getCustomColor());
374 
375         assertTemplateEquals(c1.getControlTemplate(), c2.getControlTemplate());
376     }
377 
assertTemplateEquals(ControlTemplate ct1, ControlTemplate ct2)378     private void assertTemplateEquals(ControlTemplate ct1, ControlTemplate ct2) {
379         if (ct1 == null) {
380             assertNull(ct2);
381             return;
382         } else {
383             assertNotNull(ct2);
384         }
385 
386         assertNotEquals(ct1, ControlTemplate.getErrorTemplate());
387         assertNotEquals(ct2, ControlTemplate.getErrorTemplate());
388         assertEquals(ct1.getTemplateType(), ct2.getTemplateType());
389         assertEquals(ct1.getTemplateId(), ct2.getTemplateId());
390 
391         switch (ct1.getTemplateType()) {
392             case ControlTemplate.TYPE_TOGGLE:
393                 assertToggleTemplate((ToggleTemplate) ct1, (ToggleTemplate) ct2);
394                 break;
395             case ControlTemplate.TYPE_RANGE:
396                 assertRangeTemplate((RangeTemplate) ct1, (RangeTemplate) ct2);
397                 break;
398             case ControlTemplate.TYPE_TEMPERATURE:
399                 assertTemperatureControlTemplate((TemperatureControlTemplate) ct1,
400                         (TemperatureControlTemplate) ct2);
401                 break;
402             case ControlTemplate.TYPE_TOGGLE_RANGE:
403                 assertToggleRangeTemplate((ToggleRangeTemplate) ct1, (ToggleRangeTemplate) ct2);
404                 break;
405         }
406     }
407 
assertToggleTemplate(ToggleTemplate t1, ToggleTemplate t2)408     private void assertToggleTemplate(ToggleTemplate t1, ToggleTemplate t2) {
409         assertEquals(t1.isChecked(), t2.isChecked());
410         assertEquals(t1.getContentDescription(), t2.getContentDescription());
411     }
412 
assertRangeTemplate(RangeTemplate t1, RangeTemplate t2)413     private void assertRangeTemplate(RangeTemplate t1, RangeTemplate t2) {
414         assertEquals(t1.getMinValue(), t2.getMinValue(), 0.0f);
415         assertEquals(t1.getMaxValue(), t2.getMaxValue(), 0.0f);
416         assertEquals(t1.getCurrentValue(), t2.getCurrentValue(), 0.0f);
417         assertEquals(t1.getStepValue(), t2.getStepValue(), 0.0f);
418         assertEquals(t1.getFormatString(), t2.getFormatString());
419     }
420 
assertTemperatureControlTemplate(TemperatureControlTemplate t1, TemperatureControlTemplate t2)421     private void assertTemperatureControlTemplate(TemperatureControlTemplate t1,
422             TemperatureControlTemplate t2) {
423         assertEquals(t1.getCurrentMode(), t2.getCurrentMode());
424         assertEquals(t1.getCurrentActiveMode(), t2.getCurrentActiveMode());
425         assertEquals(t1.getModes(), t2.getModes());
426         assertTemplateEquals(t1.getTemplate(), t2.getTemplate());
427     }
428 
assertToggleRangeTemplate(ToggleRangeTemplate t1, ToggleRangeTemplate t2)429     private void assertToggleRangeTemplate(ToggleRangeTemplate t1, ToggleRangeTemplate t2) {
430         assertEquals(t1.isChecked(), t2.isChecked());
431         assertEquals(t1.getActionDescription(), t2.getActionDescription());
432         assertRangeTemplate(t1.getRange(), t2.getRange());
433     }
434 }
435