blob: 63225d666e8bd60bd368c17468f51e797516c390 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.controls.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
import android.service.controls.Control;
import android.service.controls.ControlsProviderService;
import android.service.controls.DeviceTypes;
import android.service.controls.actions.BooleanAction;
import android.service.controls.actions.CommandAction;
import android.service.controls.actions.ControlAction;
import android.service.controls.actions.FloatAction;
import android.service.controls.actions.ModeAction;
import android.service.controls.templates.ControlButton;
import android.service.controls.templates.ControlTemplate;
import android.service.controls.templates.RangeTemplate;
import android.service.controls.templates.StatelessTemplate;
import android.service.controls.templates.TemperatureControlTemplate;
import android.service.controls.templates.ThumbnailTemplate;
import android.service.controls.templates.ToggleRangeTemplate;
import android.service.controls.templates.ToggleTemplate;
import androidx.test.InstrumentationRegistry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Flow.Publisher;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* CTS Controls Service to send known controls for testing.
*/
public class CtsControlsService extends ControlsProviderService {
private CtsControlsPublisher mUpdatePublisher;
private final List<Control> mAllControls = new ArrayList<>();
private final Map<String, Control> mControlsById = new HashMap<>();
private final Context mContext;
private final PendingIntent mPendingIntent;
private ColorStateList mColorStateList;
private Icon mIcon;
public CtsControlsService() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mPendingIntent = PendingIntent.getActivity(mContext, 1,
new Intent().setPackage(mContext.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
mIcon = Icon.createWithResource(mContext, R.drawable.ic_device_unknown);
mColorStateList = mContext.getResources().getColorStateList(R.color.custom_mower, null);
mAllControls.add(buildLight(false /* isOn */, 0.0f /* intensity */));
mAllControls.add(buildLock(false /* isLocked */));
mAllControls.add(buildRoutine());
mAllControls.add(buildThermostat(TemperatureControlTemplate.MODE_OFF));
mAllControls.add(buildMower(false /* isStarted */));
mAllControls.add(buildSwitch(false /* isOn */));
mAllControls.add(buildGate(false /* isLocked */));
mAllControls.add(buildCamera(true /* isActive */));
for (Control c : mAllControls) {
mControlsById.put(c.getControlId(), c);
}
}
public Control buildLight(boolean isOn, float intensity) {
RangeTemplate rt = new RangeTemplate("range", 0.0f, 100.0f, intensity, 1.0f, null);
ControlTemplate template =
new ToggleRangeTemplate("toggleRange", isOn, isOn ? "On" : "Off", rt);
return new Control.StatefulBuilder("light", mPendingIntent)
.setTitle("Light Title")
.setSubtitle("Light Subtitle")
.setStatus(Control.STATUS_OK)
.setStatusText(isOn ? "On" : "Off")
.setDeviceType(DeviceTypes.TYPE_LIGHT)
.setStructure("Home")
.setControlTemplate(template)
.setAuthRequired(false)
.build();
}
public Control buildSwitch(boolean isOn) {
ControlButton button = new ControlButton(isOn, isOn ? "On" : "Off");
ControlTemplate template = new ToggleTemplate("toggle", button);
return new Control.StatefulBuilder("switch", mPendingIntent)
.setTitle("Switch Title")
.setSubtitle("Switch Subtitle")
.setStatus(Control.STATUS_OK)
.setStatusText(isOn ? "On" : "Off")
.setDeviceType(DeviceTypes.TYPE_SWITCH)
.setStructure("Home")
.setControlTemplate(template)
.build();
}
public Control buildMower(boolean isStarted) {
String desc = isStarted ? "Started" : "Stopped";
ControlButton button = new ControlButton(isStarted, desc);
ControlTemplate template = new ToggleTemplate("toggle", button);
return new Control.StatefulBuilder("mower", mPendingIntent)
.setTitle("Mower Title")
.setSubtitle("Mower Subtitle")
.setStatus(Control.STATUS_OK)
.setStatusText(desc)
.setDeviceType(DeviceTypes.TYPE_MOWER)
.setStructure("Vacation")
.setZone("Outside")
.setControlTemplate(template)
.setCustomIcon(mIcon)
.setCustomColor(mColorStateList)
.build();
}
public Control buildLock(boolean isLocked) {
String desc = isLocked ? "Locked" : "Unlocked";
ControlButton button = new ControlButton(isLocked, desc);
ControlTemplate template = new ToggleTemplate("toggle", button);
return new Control.StatefulBuilder("lock", mPendingIntent)
.setTitle("Lock Title")
.setSubtitle("Lock Subtitle")
.setStatus(Control.STATUS_OK)
.setStatusText(desc)
.setDeviceType(DeviceTypes.TYPE_LOCK)
.setControlTemplate(template)
.build();
}
public Control buildGate(boolean isLocked) {
String desc = isLocked ? "Locked" : "Unlocked";
ControlButton button = new ControlButton(isLocked, desc);
ControlTemplate template = new ToggleTemplate("toggle", button);
return new Control.StatefulBuilder("gate", mPendingIntent)
.setTitle("Gate Title")
.setSubtitle("Gate Subtitle")
.setStatus(Control.STATUS_OK)
.setStatusText(desc)
.setDeviceType(DeviceTypes.TYPE_GATE)
.setControlTemplate(template)
.setStructure("Other home")
.build();
}
public Control buildThermostat(int mode) {
ControlTemplate template = new TemperatureControlTemplate("temperature",
ControlTemplate.getNoTemplateObject(),
mode,
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.FLAG_MODE_HEAT
| TemperatureControlTemplate.FLAG_MODE_COOL
| TemperatureControlTemplate.FLAG_MODE_OFF
| TemperatureControlTemplate.FLAG_MODE_ECO);
return new Control.StatefulBuilder("thermostat", mPendingIntent)
.setTitle("Thermostat Title")
.setSubtitle("Thermostat Subtitle")
.setStatus(Control.STATUS_OK)
.setStatusText("Off")
.setDeviceType(DeviceTypes.TYPE_THERMOSTAT)
.setControlTemplate(template)
.build();
}
public Control buildRoutine() {
ControlTemplate template = new StatelessTemplate("stateless");
return new Control.StatefulBuilder("routine", mPendingIntent)
.setTitle("Routine Title")
.setSubtitle("Routine Subtitle")
.setStatus(Control.STATUS_OK)
.setStatusText("Good Morning")
.setDeviceType(DeviceTypes.TYPE_ROUTINE)
.setControlTemplate(template)
.build();
}
public Control buildCamera(boolean active) {
String description = active ? "Live" : "Not live";
ControlTemplate template = new ThumbnailTemplate("thumbnail", active, mIcon, description);
return new Control.StatefulBuilder("camera", mPendingIntent)
.setTitle("Camera Title")
.setTitle("Camera Subtitle")
.setStatus(Control.STATUS_OK)
.setStatusText(description)
.setDeviceType(DeviceTypes.TYPE_CAMERA)
.setControlTemplate(template)
.build();
}
@Override
public Publisher<Control> createPublisherForAllAvailable() {
return new CtsControlsPublisher(mAllControls.stream()
.map(c -> new Control.StatelessBuilder(c).build())
.collect(Collectors.toList()));
}
@Override
public Publisher<Control> createPublisherForSuggested() {
return new CtsControlsPublisher(mAllControls.stream()
.map(c -> new Control.StatelessBuilder(c).build())
.collect(Collectors.toList()));
}
@Override
public Publisher<Control> createPublisherFor(List<String> controlIds) {
mUpdatePublisher = new CtsControlsPublisher(null);
for (String id : controlIds) {
Control control = mControlsById.get(id);
if (control == null) continue;
mUpdatePublisher.onNext(control);
}
return mUpdatePublisher;
}
@Override
public void performControlAction(String controlId, ControlAction action,
Consumer<Integer> consumer) {
Control c = mControlsById.get(controlId);
if (c == null) return;
// all values are hardcoded for this test
assertEquals(action.getTemplateId(), "action");
assertNotEquals(action, ControlAction.getErrorAction());
Control.StatefulBuilder builder = controlToBuilder(c);
// Modify the builder in order to update the Control to have predefined, verifiable behavior
if (action instanceof BooleanAction) {
BooleanAction b = (BooleanAction) action;
if (c.getDeviceType() == DeviceTypes.TYPE_LIGHT) {
RangeTemplate rt = new RangeTemplate("range",
0.0f /* minValue */,
100.0f /* maxValue */,
50.0f /* currentValue */,
1.0f /* step */, null);
String desc = b.getNewState() ? "On" : "Off";
builder.setStatusText(desc);
builder.setControlTemplate(new ToggleRangeTemplate("toggleRange", b.getNewState(),
desc, rt));
} else if (c.getDeviceType() == DeviceTypes.TYPE_ROUTINE) {
builder.setStatusText("Running");
builder.setControlTemplate(new StatelessTemplate("stateless"));
} else if (c.getDeviceType() == DeviceTypes.TYPE_SWITCH) {
String desc = b.getNewState() ? "On" : "Off";
builder.setStatusText(desc);
ControlButton button = new ControlButton(b.getNewState(), desc);
builder.setControlTemplate(new ToggleTemplate("toggle", button));
} else if (c.getDeviceType() == DeviceTypes.TYPE_LOCK) {
String value = action.getChallengeValue();
if (value != null && value.equals("1234")) {
String desc = b.getNewState() ? "Locked" : "Unlocked";
ControlButton button = new ControlButton(b.getNewState(), desc);
builder.setStatusText(desc);
builder.setControlTemplate(new ToggleTemplate("toggle", button));
} else {
consumer.accept(ControlAction.RESPONSE_CHALLENGE_PIN);
return;
}
} else if (c.getDeviceType() == DeviceTypes.TYPE_GATE) {
String value = action.getChallengeValue();
if (value != null && value.equals("abc123")) {
String desc = b.getNewState() ? "Locked" : "Unlocked";
ControlButton button = new ControlButton(b.getNewState(), desc);
builder.setStatusText(desc);
builder.setControlTemplate(new ToggleTemplate("toggle", button));
} else {
consumer.accept(ControlAction.RESPONSE_CHALLENGE_PASSPHRASE);
return;
}
} else if (c.getDeviceType() == DeviceTypes.TYPE_MOWER) {
String value = action.getChallengeValue();
if (value != null && value.equals("true")) {
String desc = b.getNewState() ? "Started" : "Stopped";
ControlButton button = new ControlButton(b.getNewState(), desc);
builder.setStatusText(desc);
builder.setControlTemplate(new ToggleTemplate("toggle", button));
} else {
consumer.accept(ControlAction.RESPONSE_CHALLENGE_ACK);
return;
}
}
} else if (action instanceof FloatAction) {
FloatAction f = (FloatAction) action;
if (c.getDeviceType() == DeviceTypes.TYPE_LIGHT) {
RangeTemplate rt = new RangeTemplate("range", 0.0f, 100.0f, f.getNewValue(), 1.0f,
null);
ToggleRangeTemplate trt = (ToggleRangeTemplate) c.getControlTemplate();
String desc = trt.getActionDescription().toString();
boolean state = trt.isChecked();
builder.setStatusText(desc);
builder.setControlTemplate(new ToggleRangeTemplate("toggleRange", state, desc, rt));
}
} else if (action instanceof ModeAction) {
ModeAction m = (ModeAction) action;
if (c.getDeviceType() == DeviceTypes.TYPE_THERMOSTAT) {
ControlTemplate template = new TemperatureControlTemplate("temperature",
ControlTemplate.getNoTemplateObject(),
m.getNewMode(),
TemperatureControlTemplate.MODE_OFF,
TemperatureControlTemplate.FLAG_MODE_HEAT
| TemperatureControlTemplate.FLAG_MODE_COOL
| TemperatureControlTemplate.FLAG_MODE_OFF
| TemperatureControlTemplate.FLAG_MODE_ECO);
builder.setControlTemplate(template);
}
} else if (action instanceof CommandAction) {
builder.setControlTemplate(new StatelessTemplate("stateless"));
}
// Finally build and send the default OK status
Control updatedControl = builder.build();
mControlsById.put(controlId, updatedControl);
mUpdatePublisher.onNext(updatedControl);
consumer.accept(ControlAction.RESPONSE_OK);
}
private Control.StatefulBuilder controlToBuilder(Control c) {
return new Control.StatefulBuilder(c.getControlId(), c.getAppIntent())
.setTitle(c.getTitle())
.setSubtitle(c.getSubtitle())
.setStructure(c.getStructure())
.setDeviceType(c.getDeviceType())
.setZone(c.getZone())
.setCustomIcon(c.getCustomIcon())
.setCustomColor(c.getCustomColor())
.setStatus(c.getStatus())
.setStatusText(c.getStatusText())
.setAuthRequired(c.isAuthRequired());
}
}