summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/io/trygvis/android/Optional.java45
-rw-r--r--app/src/main/java/io/trygvis/android/bt/BtCallback.java2
-rw-r--r--app/src/main/java/io/trygvis/android/bt/BtDevice.java35
-rw-r--r--app/src/main/java/io/trygvis/android/bt/BtPromise.java80
-rw-r--r--app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java63
-rw-r--r--app/src/main/java/io/trygvis/soilmoisture/MainActivity.java16
-rw-r--r--app/src/main/java/io/trygvis/soilmoisture/SmDevice.java21
-rw-r--r--app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java4
-rw-r--r--app/src/main/res/values/strings.xml1
9 files changed, 173 insertions, 94 deletions
diff --git a/app/src/main/java/io/trygvis/android/Optional.java b/app/src/main/java/io/trygvis/android/Optional.java
new file mode 100644
index 0000000..616f9a7
--- /dev/null
+++ b/app/src/main/java/io/trygvis/android/Optional.java
@@ -0,0 +1,45 @@
+package io.trygvis.android;
+
+public final class Optional<T> {
+ private final T value;
+
+ public Optional(T value) {
+ this.value = value;
+ }
+
+ public T get() {
+ if (value == null) {
+ throw new IllegalStateException("get() on empty");
+ }
+
+ return value;
+ }
+
+ public boolean isPresent() {
+ return value != null;
+ }
+
+ public void ifPresent(Consumer<T> consumer) {
+ if (value == null) {
+ return;
+ }
+
+ consumer.accept(value);
+ }
+
+ public static <T> Optional<T> of(T t) {
+ if (t == null) {
+ throw new IllegalArgumentException("t can't be null");
+ }
+
+ return new Optional<>(t);
+ }
+
+ public static <T> Optional<T> empty() {
+ return new Optional<>(null);
+ }
+
+ public static <T> Optional<T> ofNullable(T t) {
+ return t != null ? of(t) : empty();
+ }
+}
diff --git a/app/src/main/java/io/trygvis/android/bt/BtCallback.java b/app/src/main/java/io/trygvis/android/bt/BtCallback.java
index 7ff8733..3b1fb10 100644
--- a/app/src/main/java/io/trygvis/android/bt/BtCallback.java
+++ b/app/src/main/java/io/trygvis/android/bt/BtCallback.java
@@ -51,7 +51,7 @@ public class BtCallback {
throw new NotOverriddenException();
}
- public PromiseResult onDirect(Object value) {
+ public PromiseResult onDirect(BluetoothGatt value) {
throw new NotOverriddenException();
}
diff --git a/app/src/main/java/io/trygvis/android/bt/BtDevice.java b/app/src/main/java/io/trygvis/android/bt/BtDevice.java
index 6a4d92e..23fd397 100644
--- a/app/src/main/java/io/trygvis/android/bt/BtDevice.java
+++ b/app/src/main/java/io/trygvis/android/bt/BtDevice.java
@@ -12,7 +12,8 @@ import java.util.Date;
import static io.trygvis.android.bt.BtPromise.BtBluetoothGattCallback;
import static io.trygvis.android.bt.BtPromise.PromiseResult.detour;
-import static io.trygvis.android.bt.BtPromise.PromiseResult.stop;
+import static io.trygvis.android.bt.BtPromise.PromiseResult.fail;
+import static io.trygvis.android.bt.BtPromise.PromiseResult.waitForNextEvent;
public class BtDevice<A> {
private final static String TAG = BtDevice.class.getSimpleName();
@@ -90,22 +91,10 @@ public class BtDevice<A> {
this.lastSeen = lastSeen;
}
-// public synchronized boolean connect(BtPromise executor) {
-// Log.i(TAG, "connect(), address=" + bluetoothDevice.getAddress());
-// BluetoothGattCallback callback = executor.asCallback(bluetoothDevice.getAddress());
-// gatt = bluetoothDevice.connectGatt(btService, false, new WrappingBluetoothGattCallback(callback) {
-// @Override
-// public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
-// BtDevice.this.connected = newState == BluetoothGatt.STATE_CONNECTED;
-//
-// super.onConnectionStateChange(gatt, status, newState);
-// }
-// });
-// return true;
-// }
-
/**
* The first handler must handle a onDirect().
+ * <p>
+ * Services will be discovered.
*/
public synchronized void withConnection(BtPromise promise) {
if (callback != null) {
@@ -124,22 +113,26 @@ public class BtDevice<A> {
btService.sendBroadcast(btService.createDeviceConnection(address));
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
- Log.i(TAG, "Connected to " + address);
- return detour(promise);
+ Log.i(TAG, "Connected to " + address + ", discovering services");
+ return gatt.discoverServices() ? waitForNextEvent() : fail();
} else {
Log.i(TAG, "Could not connect to " + address + ", trying again");
return detour(new BtPromise().onConnectionStateChange((gatt2, status2, newState2) -> {
if (status2 == BluetoothGatt.GATT_SUCCESS && newState2 == BluetoothGatt.STATE_CONNECTED) {
- Log.i(TAG, "Connected to " + address);
- return detour(promise);
+ Log.i(TAG, "Connected to " + address + ", discovering services");
+ return gatt.discoverServices() ? waitForNextEvent() : fail();
}
Log.i(TAG, "Could still not connect to " + address + ", failing.");
- return stop();
+ return fail();
}));
}
+ }).
+ onServicesDiscovered(gatt -> {
+ Log.i(TAG, "Services discovered, has " + gatt.getServices().size() + " services");
+ return detour(promise);
});
} else {
newPromise = promise;
@@ -157,7 +150,7 @@ public class BtDevice<A> {
} else {
callback.onEvent(BtPromise.EventType.onDirect, "", gatt, null, null,
BluetoothGatt.GATT_SUCCESS,
- BluetoothGatt.STATE_CONNECTED, gatt);
+ BluetoothGatt.STATE_CONNECTED);
}
}
diff --git a/app/src/main/java/io/trygvis/android/bt/BtPromise.java b/app/src/main/java/io/trygvis/android/bt/BtPromise.java
index acbed8d..91c0359 100644
--- a/app/src/main/java/io/trygvis/android/bt/BtPromise.java
+++ b/app/src/main/java/io/trygvis/android/bt/BtPromise.java
@@ -33,6 +33,8 @@ public class BtPromise {
private static final PromiseResult waitForNextEvent = new WaitForNextEvent();
private static final PromiseResult stop = new Stop();
+ private static final PromiseResult fail = new Fail();
+ private static final PromiseResult continueDirectly = new ContinueDirectly();
private Boolean stopOnFailure;
@@ -44,14 +46,18 @@ public class BtPromise {
return waitForNextEvent;
}
- public static PromiseResult continueDirectly(Object value) {
- return new ContinueDirectly(value);
+ public static PromiseResult continueDirectly() {
+ return continueDirectly;
}
public static PromiseResult stop() {
return stop;
}
+ public static PromiseResult fail() {
+ return fail;
+ }
+
public static PromiseResult detour(BtPromise promise) {
return new Detour(promise);
}
@@ -61,16 +67,14 @@ public class BtPromise {
}
private static class ContinueDirectly extends PromiseResult {
- private final Object value;
-
- private ContinueDirectly(Object value) {
- this.value = value;
- }
}
private static class Stop extends PromiseResult {
}
+ private static class Fail extends PromiseResult {
+ }
+
private static class Detour extends PromiseResult {
final BtPromise promise;
@@ -183,10 +187,10 @@ public class BtPromise {
return this;
}
- public synchronized BtPromise onDirect(Function<Object, PromiseResult> callback) {
+ public synchronized BtPromise onDirect(Function<BluetoothGatt, PromiseResult> callback) {
actionQ.add(new BtCallback(stopOnFailure(), "onDirect") {
@Override
- public PromiseResult onDirect(Object value) {
+ public PromiseResult onDirect(BluetoothGatt value) {
return callback.apply(value);
}
});
@@ -255,7 +259,7 @@ public class BtPromise {
private static PromiseResult callCallback(EventType key, BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
BluetoothGattDescriptor descriptor, int status, int newState,
- BtCallback btCallback, Object value) {
+ BtCallback btCallback) {
switch (key) {
case onConnectionStateChange:
return btCallback.onConnectionStateChange(gatt, status, newState);
@@ -275,7 +279,7 @@ public class BtPromise {
return btCallback.onReliableWriteCompleted(gatt);
case onDirect:
- return btCallback.onDirect(value);
+ return btCallback.onDirect(gatt);
case onFailure:
btCallback.onFailure();
return null;
@@ -306,7 +310,7 @@ public class BtPromise {
}
void onEvent(EventType key, String values, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
- BluetoothGattDescriptor descriptor, int status, int newState, Object value) {
+ BluetoothGattDescriptor descriptor, int status, int newState) {
boolean success = status == BluetoothGatt.GATT_SUCCESS;
events.add(key + "(" + values + "), success=" + success);
@@ -317,7 +321,7 @@ public class BtPromise {
if (!hasNext()) {
Log.d(TAG, "All Bluetooth actions are done, no handler for last event.");
- doFinally();
+ doFinally(true);
return;
}
@@ -326,7 +330,7 @@ public class BtPromise {
if (!success) {
if (btCallback.stopOnFailure) {
// doFailure();
- doFinally();
+ doFinally(false);
return;
} else {
Log.i(TAG, "Last status was a failure, but the callback still want it.");
@@ -343,12 +347,18 @@ public class BtPromise {
try {
Log.i(TAG, "Executing bt action: " + btCallback.name);
- PromiseResult result = callCallback(key, gatt, characteristic, descriptor, status, newState, btCallback,
- value);
+ PromiseResult result = callCallback(key, gatt, characteristic, descriptor, status, newState, btCallback
+ );
if (result instanceof Stop) {
Log.i(TAG, "The chain want to stop.");
- doFinally();
+ doFinally(true);
+ return;
+ }
+
+ if (result instanceof Fail) {
+ Log.i(TAG, "The chain returned fail().");
+ doFinally(false);
return;
}
@@ -370,20 +380,18 @@ public class BtPromise {
// current stack can continue.
actionQ.addAll(currentAction, detour.actionQ);
-// failureQ.addAll(detour.failureQ);
+// failureQ.addAll(detour.failureQ);B
finallyQ.addAll(detour.finallyQ);
Log.i(TAG, "hasNext(): " + hasNext());
if (hasNext()) {
Log.i(TAG, "next action: " + actionQ.get(currentAction).name);
}
- result = PromiseResult.continueDirectly(gatt);
+ result = PromiseResult.continueDirectly();
}
if (result instanceof ContinueDirectly) {
- value = ((ContinueDirectly) result).value;
- onEvent(onDirect, "value=" + value, null, null, null, BluetoothGatt.GATT_SUCCESS, 0,
- value);
+ onEvent(onDirect, "direct", gatt, null, null, BluetoothGatt.GATT_SUCCESS, 0);
return;
}
@@ -394,7 +402,11 @@ public class BtPromise {
} catch (NotOverriddenException e) {
Log.w(TAG, "Unexpected callback by listener: " + key);
// doFailure();
- doFinally();
+ doFinally(false);
+ } catch (Exception e) {
+ Log.w(TAG, "Exception in callback", e);
+// doFailure();
+ doFinally(false);
}
}
@@ -442,16 +454,16 @@ public class BtPromise {
Log.w(TAG, msg.toString());
}
- private void doFinally() {
+ private void doFinally(boolean success) {
showEvents();
actionQ.clear();
- Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers");
+ Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers, success=" + success);
for (BtCallback callback : finallyQ) {
try {
- callCallback(onFinally, null, null, null, 0, 0, callback, null);
+ callCallback(onFinally, null, null, null, 0, 0, callback);
} catch (NotOverriddenException e) {
return;
}
@@ -464,42 +476,42 @@ public class BtPromise {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
- onEvent(onConnectionStateChange, "status=" + status + ", newState=" + newState, gatt, null, null, status, newState, null);
+ onEvent(onConnectionStateChange, "status=" + status + ", newState=" + newState, gatt, null, null, status, newState);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
- onEvent(onServicesDiscovered, "status=" + status, gatt, null, null, status, 9, null);
+ onEvent(onServicesDiscovered, "status=" + status, gatt, null, null, status, 9);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
- onEvent(onCharacteristicRead, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0, null);
+ onEvent(onCharacteristicRead, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
- onEvent(onCharacteristicWrite, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0, null);
+ onEvent(onCharacteristicWrite, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
- onEvent(onCharacteristicChanged, "characteristic=" + characteristic.getUuid(), gatt, characteristic, null, BluetoothGatt.GATT_SUCCESS, 0, null);
+ onEvent(onCharacteristicChanged, "characteristic=" + characteristic.getUuid(), gatt, characteristic, null, BluetoothGatt.GATT_SUCCESS, 0);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
- onEvent(onDescriptorRead, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0, null);
+ onEvent(onDescriptorRead, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
- onEvent(onDescriptorWrite, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0, null);
+ onEvent(onDescriptorWrite, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0);
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
- onEvent(onReliableWriteCompleted, "status=" + status, gatt, null, null, status, 0, null);
+ onEvent(onReliableWriteCompleted, "status=" + status, gatt, null, null, status, 0);
}
@Override
diff --git a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java
index 992bf97..25dac5c 100644
--- a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java
+++ b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java
@@ -1,7 +1,6 @@
package io.trygvis.soilmoisture;
import android.app.Service;
-import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
@@ -13,6 +12,7 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.IBinder;
import android.util.Log;
+import android.widget.Toast;
import java.util.ArrayList;
import java.util.Comparator;
@@ -133,14 +133,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS
Log.i(TAG, "Probing " + address + ", name=" + btDevice.getName());
BtPromise executor = new BtPromise().
- onDirect(v -> {
- Log.i(TAG, "Discovering services");
- BluetoothGatt gatt = (BluetoothGatt) v;
- return gatt.discoverServices() ? waitForNextEvent() : stop();
- }).
- onServicesDiscovered(gatt -> {
- Log.i(TAG, "Services discovered");
-
+ onDirect(gatt -> {
BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE);
if (service == null) {
@@ -270,19 +263,44 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS
// -----------------------------------------------------------------------
private SmDevice createTag(SQLiteDatabase db, BtDevice<SmDevice> btDevice) {
- Cursor cursor = db.query(Tables.T_SM_DEVICE, new String[]{Tables.C_ID}, Tables.C_BT_DEVICE + "=?",
- new String[]{valueOf(btDevice.getId())}, null, null, null);
+ Cursor cursor = null;
+ try {
+ cursor = db.query(Tables.T_SM_DEVICE, new String[]{Tables.C_ID}, Tables.C_BT_DEVICE + "=?",
+ new String[]{valueOf(btDevice.getId())}, null, null, null);
+
+ long id;
+ if (cursor.moveToNext()) {
+ id = cursor.getLong(0);
+ } else {
+ ContentValues values = new ContentValues();
+ values.put(Tables.C_BT_DEVICE, btDevice.getId());
+ id = db.insert(Tables.T_SM_DEVICE, null, values);
+ }
- long id;
- if (cursor.moveToNext()) {
- id = cursor.getLong(0);
- } else {
- ContentValues values = new ContentValues();
- values.put(Tables.C_BT_DEVICE, btDevice.getId());
- id = db.insert(Tables.T_SM_DEVICE, null, values);
- }
+ cursor.close();
+
+ String[] sensorColumns = {
+ Tables.C_ID,
+ Tables.C_INDEX,
+ };
+ cursor = db.query(Tables.T_SM_SENSOR, sensorColumns, Tables.C_SM_DEVICE + "=?",
+ new String[]{valueOf(id)}, null, null, Tables.C_INDEX);
- return new SmDevice(this, btDevice, id);
+ SmDevice device = new SmDevice(this, btDevice, id);
+
+ if (cursor.moveToNext()) {
+ device.setIsUseful(true);
+ do {
+ device.addSensor(new SmSensor(device, cursor.getLong(0), cursor.getInt(1)));
+ } while (cursor.moveToNext());
+ }
+
+ return device;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
}
void handleNewSensorValueReady(SmSensor sensor, int value) {
@@ -302,8 +320,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS
void readCurrentValue(SmSensor sensor) {
BtPromise promise = new BtPromise().
- onDirect(v -> {
- BluetoothGatt gatt = (BluetoothGatt) v;
+ onDirect(gatt -> {
BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE);
BluetoothGattCharacteristic soilMoisture = service.getCharacteristic(TrygvisIoUuids.Characteristics.SOIL_MOISTURE);
@@ -328,6 +345,8 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS
return stop();
}).
onFinally(() -> {
+ Toast.makeText(context, R.string.error_could_not_read_value, Toast.LENGTH_SHORT).
+ show();
});
sensor.getDevice().getBtDevice().withConnection(promise);
diff --git a/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java b/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java
index e9ecae2..c6293c6 100644
--- a/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java
+++ b/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java
@@ -219,12 +219,6 @@ public class MainActivity extends ListActivity {
public static class BtDeviceDialogFragment extends DialogFragment {
private MainActivity mainActivity;
-// private final BtDevice<SmDevice> device;
-//
-// public BtDeviceDialogFragment(MainActivity mainActivity, BtDevice<SmDevice> device) {
-// this.mainActivity = mainActivity;
-// this.device = device;
-// }
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
@@ -472,7 +466,7 @@ public class MainActivity extends ListActivity {
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
- Log.i(TAG, "getView, position=" + position + ", view=" + view);
+// Log.i(TAG, "getView, position=" + position + ", view=" + view);
Object o = current.get(position);
if (o instanceof BtDevice) {
@@ -491,8 +485,6 @@ public class MainActivity extends ListActivity {
if (view == null) {
view = inflater.inflate(R.layout.fragment_device, null);
view.setTag(new DeviceItem(view));
- view.setClickable(false);
- view.setLongClickable(true);
view.setOnLongClickListener(v -> MainActivity.this.onBtDeviceLongClick(device));
}
@@ -584,11 +576,7 @@ public class MainActivity extends ListActivity {
text += ", value: " + (value == null ? "Unknown" : value);
item.description.setText(text);
- if (value != null) {
- item.sensorProgress.setProgress(value);
- } else {
- item.sensorProgress.setIndeterminate(true);
- }
+ item.sensorProgress.setProgress(value != null ? value : 0);
return view;
}
diff --git a/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java b/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java
index 1074dd9..d56f33e 100644
--- a/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java
+++ b/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java
@@ -6,8 +6,11 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
+import io.trygvis.android.Optional;
import io.trygvis.android.bt.BtDevice;
+import static io.trygvis.android.Optional.empty;
+import static io.trygvis.android.Optional.of;
import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_SENSOR_COUNT;
import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_SENSOR_NAME;
import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_VALUE;
@@ -75,7 +78,25 @@ class SmDevice {
return sensors;
}
+ public Optional<SmSensor> getSensorByIndex(int index) {
+ if (!isUseful()) {
+ throw new IllegalStateException("Not a useful device");
+ }
+
+ for (SmSensor sensor : sensors) {
+ if (sensor.getIndex() == index) {
+ return of(sensor);
+ }
+ }
+
+ return empty();
+ }
+
void addSensor(SmSensor sensor) {
+ if (getSensorByIndex(sensor.index).isPresent()) {
+ throw new IllegalStateException("This device already contains a sensor with index=" + sensor.index);
+ }
+
sensors.add(sensor);
}
diff --git a/app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java b/app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java
index a287c7b..5e3ddbb 100644
--- a/app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java
+++ b/app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java
@@ -41,10 +41,10 @@ public interface SoilMoistureService {
public void onScanStarted() {
}
- public void onNewDevice(String address) {
+ public void onScanStopped() {
}
- public void onScanStopped() {
+ public void onNewDevice(String address) {
}
public void onNewSample(String address, int sensor) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2dc3820..26f42fe 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,5 +36,6 @@
<string name="dialog_probe_device">Probe device?</string>
<string name="yes">Yes</string>
<string name="no">No</string>
+ <string name="error_could_not_read_value">Could not read value</string>
</resources>