Display HR firmware version

Hide fw,hw,hr versions by default and show them on demand with an info
button.
here
cpfeiffer 2016-03-16 00:14:38 +01:00
parent 4aaf3dd162
commit e26e6d7b24
16 changed files with 336 additions and 95 deletions

View File

@ -8,14 +8,19 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.text.Collator;
import java.util.Collections;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
/**
* Adapter for displaying GBDevice instances.
@ -32,7 +37,7 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
@Override
public View getView(int position, View view, ViewGroup parent) {
GBDevice device = getItem(position);
final GBDevice device = getItem(position);
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
@ -42,37 +47,59 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
}
TextView deviceStatusLabel = (TextView) view.findViewById(R.id.device_status);
TextView deviceNameLabel = (TextView) view.findViewById(R.id.device_name);
TextView deviceInfo1Label = (TextView) view.findViewById(R.id.device_info1);
TextView deviceInfo2Label = (TextView) view.findViewById(R.id.device_info2);
final ListView deviceInfoList = (ListView) view.findViewById(R.id.device_item_infos);
ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos());
infoAdapter.setHorizontalAlignment(true);
deviceInfoList.setAdapter(infoAdapter);
TextView batteryLabel = (TextView) view.findViewById(R.id.battery_label);
TextView batteryStatusLabel = (TextView) view.findViewById(R.id.battery_status);
ImageView deviceImageView = (ImageView) view.findViewById(R.id.device_image);
final ImageView deviceImageView = (ImageView) view.findViewById(R.id.device_image);
ImageView deviceInfoView = (ImageView) view.findViewById(R.id.device_info_image);
ProgressBar busyIndicator = (ProgressBar) view.findViewById(R.id.device_busy_indicator);
deviceNameLabel.setText(getUniqueDeviceName(device));
deviceInfo1Label.setText(device.getHWInfoString());
deviceInfo2Label.setText(device.getInfoString());
if (device.isBusy()) {
deviceStatusLabel.setText(device.getBusyTask());
busyIndicator.setVisibility(View.VISIBLE);
batteryLabel.setVisibility(View.GONE);
batteryStatusLabel.setVisibility(View.GONE);
deviceInfo1Label.setVisibility(View.GONE);
deviceInfo2Label.setVisibility(View.GONE);
} else {
deviceStatusLabel.setText(device.getStateString());
busyIndicator.setVisibility(View.GONE);
batteryLabel.setVisibility(View.VISIBLE);
batteryStatusLabel.setVisibility(View.VISIBLE);
deviceInfo1Label.setVisibility(View.VISIBLE);
deviceInfo2Label.setVisibility(View.VISIBLE);
}
boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
deviceInfoView.setVisibility(showInfoIcon ? View.VISIBLE : View.GONE);
deviceInfoView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (deviceInfoList.getVisibility() == View.VISIBLE) {
deviceInfoList.setVisibility(View.GONE);
} else {
ArrayAdapter adapter = (ArrayAdapter) deviceInfoList.getAdapter();
adapter.clear();
List<ItemWithDetails> infos = device.getDeviceInfos();
Collections.sort(infos);
adapter.addAll(infos);
justifyListViewHeightBasedOnChildren(deviceInfoList);
deviceInfoList.setVisibility(View.VISIBLE);
}
}
});
short batteryLevel = device.getBatteryLevel();
if (batteryLevel != GBDevice.BATTERY_UNKNOWN) {
batteryStatusLabel.setText("BAT: " + device.getBatteryLevel() + "%");
batteryLabel.setText("BAT:");
batteryStatusLabel.setText(device.getBatteryLevel() + "%");
BatteryState batteryState = device.getBatteryState();
if (BatteryState.BATTERY_LOW.equals(batteryState)) {
batteryLabel.setTextColor(Color.RED);
batteryStatusLabel.setTextColor(Color.RED);
} else {
batteryLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.secondarytext));
batteryStatusLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.secondarytext));
if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
@ -81,6 +108,7 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
}
}
} else {
batteryLabel.setText("");
batteryStatusLabel.setText("");
}
@ -98,11 +126,35 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
return view;
}
public void justifyListViewHeightBasedOnChildren (ListView listView) {
ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter();
if (adapter == null) {
return;
}
ViewGroup vg = listView;
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, vg);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams par = listView.getLayoutParams();
par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
listView.setLayoutParams(par);
listView.requestLayout();
}
private String getUniqueDeviceName(GBDevice device) {
String deviceName = device.getName();
if (!isUniqueDeviceName(device, deviceName)) {
deviceName = deviceName + " " + device.getHardwareVersion();
if (!isUniqueDeviceName(device, deviceName)) {
if (device.getHardwareVersion() != null) {
deviceName = deviceName + " " + device.getHardwareVersion();
if (!isUniqueDeviceName(device, deviceName)) {
deviceName = deviceName + " " + device.getShortAddress();
}
} else {
deviceName = deviceName + " " + device.getShortAddress();
}
}

View File

@ -19,6 +19,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
private final Context context;
private boolean horizontalAlignment;
public ItemWithDetailsAdapter(Context context, List<ItemWithDetails> items) {
super(context, 0, items);
@ -26,6 +27,9 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
this.context = context;
}
public void setHorizontalAlignment(boolean horizontalAlignment) {
this.horizontalAlignment = horizontalAlignment;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
ItemWithDetails item = getItem(position);
@ -34,7 +38,11 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.item_with_details, parent, false);
if (horizontalAlignment) {
view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
} else {
view = inflater.inflate(R.layout.item_with_details, parent, false);
}
}
ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
TextView nameView = (TextView) view.findViewById(R.id.item_name);

View File

@ -10,10 +10,15 @@ import android.support.v4.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
public class GBDevice implements Parcelable {
public static final String ACTION_DEVICE_CHANGED
@ -34,6 +39,8 @@ public class GBDevice implements Parcelable {
public static final short BATTERY_UNKNOWN = -1;
private static final short BATTERY_THRESHOLD_PERCENT = 10;
public static final String EXTRA_DEVICE = "device";
private static final String DEVINFO_HW_VER = "HW: ";
private static final String DEVINFO_FW_VER = "FW: ";
private final String mName;
private final String mAddress;
private final DeviceType mDeviceType;
@ -45,6 +52,8 @@ public class GBDevice implements Parcelable {
private BatteryState mBatteryState;
private short mRssi = RSSI_UNKNOWN;
private String mBusyTask;
private String infoString;
private List<ItemWithDetails> mDeviceInfos;
public GBDevice(String address, String name, DeviceType deviceType) {
mAddress = address;
@ -65,6 +74,7 @@ public class GBDevice implements Parcelable {
mBatteryState = (BatteryState) in.readSerializable();
mRssi = (short) in.readInt();
mBusyTask = in.readString();
mDeviceInfos = in.readArrayList(getClass().getClassLoader());
validate();
}
@ -82,6 +92,7 @@ public class GBDevice implements Parcelable {
dest.writeSerializable(mBatteryState);
dest.writeInt(mRssi);
dest.writeString(mBusyTask);
dest.writeList(mDeviceInfos);
}
private void validate() {
@ -213,18 +224,6 @@ public class GBDevice implements Parcelable {
return GBApplication.getContext().getString(R.string.unknown_state);
}
public String getInfoString() {
if (mFirmwareVersion != null) {
if (mHardwareVersion != null) {
return GBApplication.getContext().getString(R.string.connectionstate_hw_fw, mHardwareVersion, mFirmwareVersion);
}
return GBApplication.getContext().getString(R.string.connectionstate_fw, mFirmwareVersion);
} else {
return "";
}
}
public DeviceType getType() {
return mDeviceType;
}
@ -330,6 +329,48 @@ public class GBDevice implements Parcelable {
return "";
}
public boolean hasDeviceInfos() {
return getDeviceInfos().size() > 0;
}
public List<ItemWithDetails> getDeviceInfos() {
List<ItemWithDetails> result = new ArrayList<>();
if (mDeviceInfos != null) {
result.addAll(mDeviceInfos);
}
if (mHardwareVersion != null) {
result.add(new GenericItem(DEVINFO_HW_VER, mHardwareVersion));
}
if (mFirmwareVersion != null) {
result.add(new GenericItem(DEVINFO_FW_VER, mFirmwareVersion));
}
return result;
}
public void setDeviceInfos(List<ItemWithDetails> deviceInfos) {
this.mDeviceInfos = deviceInfos;
}
public void addDeviceInfo(ItemWithDetails info) {
if (mDeviceInfos == null) {
mDeviceInfos = new ArrayList<>();
} else {
int index = mDeviceInfos.indexOf(info);
if (index >= 0) {
mDeviceInfos.set(index, info); // replace item with new one
return;
}
}
mDeviceInfos.add(info);
}
public boolean removeDeviceInfo(ItemWithDetails info) {
if (mDeviceInfos == null) {
return false;
}
return mDeviceInfos.remove(info);
}
public enum State {
// Note: the order is important!
NOT_CONNECTED,

View File

@ -1,10 +1,31 @@
package nodomain.freeyourgadget.gadgetbridge.model;
import android.os.Parcel;
import android.os.Parcelable;
import java.text.Collator;
public class GenericItem implements ItemWithDetails {
private String name;
private String details;
private int icon;
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<GenericItem>() {
@Override
public GenericItem createFromParcel(Parcel source) {
GenericItem item = new GenericItem();
item.setName(source.readString());
item.setDetails(source.readString());
item.setIcon(source.readInt());
return item;
}
@Override
public GenericItem[] newArray(int size) {
return new GenericItem[size];
}
};
public GenericItem(String name, String details) {
this.name = name;
this.details = details;
@ -17,6 +38,13 @@ public class GenericItem implements ItemWithDetails {
public GenericItem() {
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getName());
dest.writeString(getDetails());
dest.writeInt(getIcon());
}
public void setName(String name) {
this.name = name;
}
@ -43,4 +71,38 @@ public class GenericItem implements ItemWithDetails {
public int getIcon() {
return icon;
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GenericItem that = (GenericItem) o;
return !(getName() != null ? !getName().equals(that.getName()) : that.getName() != null);
}
@Override
public int hashCode() {
return getName() != null ? getName().hashCode() : 0;
}
@Override
public int compareTo(ItemWithDetails another) {
if (getName() == another.getName()) {
return 0;
}
if (getName() == null) {
return +1;
} else if (another.getName() == null) {
return -1;
}
return Collator.getInstance().compare(getName(), another.getName());
}
}

View File

@ -1,9 +1,17 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public interface ItemWithDetails {
import android.os.Parcelable;
public interface ItemWithDetails extends Parcelable, Comparable<ItemWithDetails> {
String getName();
String getDetails();
int getIcon();
/**
* Equality is based on #getName() only.
* @param other
*/
boolean equals(Object other);
}

View File

@ -67,14 +67,6 @@ public class DeviceInfo extends AbstractInfo {
return getInt(data, from, 4);
}
public String getHumanFirmwareVersion() {
return MiBandFWHelper.formatFirmwareVersion(fwVersion);
}
public String getHumanFirmware2Version() {
return MiBandFWHelper.formatFirmwareVersion(fw2Version);
}
public int getFirmwareVersion() {
return fwVersion;
}
@ -83,6 +75,10 @@ public class DeviceInfo extends AbstractInfo {
return fw2Version;
}
public boolean supportsHeartrate() {
return isMilli1S();
}
@Override
public String toString() {
return "DeviceInfo{" +
@ -117,8 +113,7 @@ public class DeviceInfo extends AbstractInfo {
return MiBandConst.MI_1A;
}
if (isMilli1S()) {
return getHumanFirmware2Version();
// return MiBandConst.MI_1S;
return MiBandConst.MI_1S;
}
return "?";
}

View File

@ -36,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
@ -551,7 +552,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
public boolean supportsHeartRate() {
return getDeviceInfo() != null && getDeviceInfo().isMilli1S();
return getDeviceInfo() != null && getDeviceInfo().supportsHeartrate();
}
@Override
@ -815,9 +816,12 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
private void handleDeviceInfo(byte[] value, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
mDeviceInfo = new DeviceInfo(value);
if (getDeviceInfo().supportsHeartrate()) {
getDevice().addDeviceInfo(new GenericItem("HR:", MiBandFWHelper.formatFirmwareVersion(mDeviceInfo.getHeartrateFirmwareVersion())));
}
LOG.warn("Device info: " + mDeviceInfo);
versionCmd.hwVersion = mDeviceInfo.getHwVersion();
versionCmd.fwVersion = mDeviceInfo.getHumanFirmwareVersion();
versionCmd.fwVersion = MiBandFWHelper.formatFirmwareVersion(mDeviceInfo.getFirmwareVersion());
handleGBDeviceEvent(versionCmd);
}
}
@ -936,7 +940,6 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
List<CalendarEvents.CalendarEvent> mEvents = upcomingEvents.getCalendarEventList(getContext());
int iteration = 0;
ArrayList<GBAlarm> alarmList = new ArrayList<>();
for (CalendarEvents.CalendarEvent mEvt : mEvents) {
if (iteration >= availableSlots || iteration > 2) {
break;

View File

@ -137,7 +137,7 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
public FetchActivityOperation(MiBandSupport support) {
super(support);
hasExtendedActivityData = support.getDeviceInfo().isMilli1S();
hasExtendedActivityData = support.getDeviceInfo().supportsHeartrate();
activityDataHolderSize = getBytesPerMinuteOfActivityData() * 60 * 4; // 4h
activityStruct = new ActivityStruct(activityDataHolderSize);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,8 @@
<!-- drawable/information-outline.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z" />
</vector>

View File

@ -1,75 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:columnCount="2"
android:padding="8dp">
<ImageView
android:id="@+id/device_image"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentStart="true"
android:contentDescription="@string/candidate_item_device_image" />
<GridLayout
android:background="?android:attr/activatedBackgroundIndicator"
android:paddingBottom="2dp"
android:columnCount="5"
android:layout_gravity="fill_horizontal"
android:paddingTop="3dp">
<RelativeLayout
android:layout_width="fill_parent"
<TextView
android:id="@+id/device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/device_image"
android:orientation="vertical"
android:paddingStart="8dp">
android:singleLine="true"
android:typeface="sans" />
<TextView
android:id="@+id/device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@+id/statuswrapper"
android:layout_toStartOf="@+id/device_info"
android:singleLine="true"
android:typeface="sans" />
<ProgressBar
android:id="@+id/device_busy_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_rowSpan="2"
android:indeterminate="true" />
<ProgressBar
android:id="@+id/device_busy_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:indeterminate="true" />
<android.support.v4.widget.Space
android:layout_width="wrap_content"
android:layout_rowSpan="2" />
<LinearLayout
android:id="@+id/statuswrapper"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/device_name"
android:orientation="horizontal"
android:paddingTop="3dp">
<TextView
android:id="@+id/battery_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/secondarytext"
android:textStyle="bold"
android:paddingLeft="7sp"
android:paddingRight="7sp" />
<TextView
android:id="@+id/device_status"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="start"
android:textStyle="bold" />
<ImageView
android:src="@drawable/ic_information_outline_grey600_24dp"
android:id="@+id/device_info_image"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_rowSpan="2"
android:scaleType="centerInside"
android:contentDescription="@string/candidate_item_device_image"
android:clickable="true"
android:layout_gravity="center_vertical" />
<TextView
android:id="@+id/battery_status"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="end"
android:textColor="@color/secondarytext"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/device_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/device_name"
android:layout_alignParentEnd="true"
android:gravity="end"
android:textColor="@color/secondarytext"
android:textSize="12sp" />
<TextView
android:id="@+id/device_status"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="start|bottom"
android:textStyle="bold"
android:layout_gravity="fill_horizontal" />
</RelativeLayout>
</RelativeLayout>
<TextView
android:id="@+id/battery_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start|bottom"
android:textColor="@color/secondarytext"
android:textStyle="bold"
android:layout_gravity="end"
android:paddingRight="7sp"
android:paddingLeft="7sp" />
</GridLayout>
<ListView
android:id="@+id/device_item_infos"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/widget_margin"
android:gravity="fill_horizontal"
android:layout_columnSpan="2"
android:visibility="gone"
android:layout_gravity="fill"
android:paddingLeft="36dp" />
</GridLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:paddingStart="1dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_alignParentStart="true"
android:contentDescription="@string/candidate_item_device_image" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/item_image"
android:orientation="horizontal"
android:paddingStart="2dp"
android:paddingEnd="2dp">
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollHorizontally="false"
android:textColor="@color/secondarytext"
android:textSize="12sp"
android:text="Item Name"
android:singleLine="true" />
<TextView
android:id="@+id/item_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/secondarytext"
android:textSize="12sp"
android:text="Item Description"
/>
</LinearLayout>
</RelativeLayout>

View File

@ -231,5 +231,7 @@
<string name="add_widget">Add widget</string>
<string name="activity_prefs_sleep_duration">Preferred sleep duration in hours</string>
<string name="appwidget_alarms_set">An alarm was set for %1$02d:%2$02d</string>
<string name="device_hw">HW: %1$s</string>
<string name="device_fw">FW: %1$s</string>
</resources>