diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java index 6b48ad64..b655a601 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java @@ -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 { @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 { } 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 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 { } } } else { + batteryLabel.setText(""); batteryStatusLabel.setText(""); } @@ -98,11 +126,35 @@ public class GBDeviceAdapter extends ArrayAdapter { 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(); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java index dd1ab2da..fed094ac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java @@ -19,6 +19,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails; public class ItemWithDetailsAdapter extends ArrayAdapter { private final Context context; + private boolean horizontalAlignment; public ItemWithDetailsAdapter(Context context, List items) { super(context, 0, items); @@ -26,6 +27,9 @@ public class ItemWithDetailsAdapter extends ArrayAdapter { 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 { 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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java index 9e832494..e296ad98 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java @@ -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 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 getDeviceInfos() { + List 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 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, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GenericItem.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GenericItem.java index f2350768..1e28c869 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GenericItem.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GenericItem.java @@ -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() { + @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()); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java index dcb76883..0c5dc497 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java @@ -1,9 +1,17 @@ package nodomain.freeyourgadget.gadgetbridge.model; -public interface ItemWithDetails { +import android.os.Parcelable; + +public interface ItemWithDetails extends Parcelable, Comparable { String getName(); String getDetails(); int getIcon(); + + /** + * Equality is based on #getName() only. + * @param other + */ + boolean equals(Object other); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java index 5a704497..74450139 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java @@ -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 "?"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index 53272f32..be8b6182 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -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 mEvents = upcomingEvents.getCalendarEventList(getContext()); int iteration = 0; - ArrayList alarmList = new ArrayList<>(); for (CalendarEvents.CalendarEvent mEvt : mEvents) { if (iteration >= availableSlots || iteration > 2) { break; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java index e5e14441..42faa337 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java @@ -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); } diff --git a/app/src/main/res/drawable-hdpi/ic_information_outline_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_information_outline_grey600_24dp.png new file mode 100644 index 00000000..28ac3bdd Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_information_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_information_outline_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_information_outline_grey600_24dp.png new file mode 100644 index 00000000..213286b7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_information_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_information_outline_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_information_outline_grey600_24dp.png new file mode 100644 index 00000000..34b5a962 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_information_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_information_outline_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_information_outline_grey600_24dp.png new file mode 100644 index 00000000..fb2acbc6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_information_outline_grey600_24dp.png differ diff --git a/app/src/main/res/drawable/information_outline.xml b/app/src/main/res/drawable/information_outline.xml new file mode 100644 index 00000000..897c21e2 --- /dev/null +++ b/app/src/main/res/drawable/information_outline.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/device_item.xml b/app/src/main/res/layout/device_item.xml index 246dca8d..d7b4b1bb 100644 --- a/app/src/main/res/layout/device_item.xml +++ b/app/src/main/res/layout/device_item.xml @@ -1,75 +1,93 @@ - + - + android:singleLine="true" + android:typeface="sans" /> - + - + - + - + - - - + - - \ No newline at end of file + + + + + diff --git a/app/src/main/res/layout/item_with_details_horizontal.xml b/app/src/main/res/layout/item_with_details_horizontal.xml new file mode 100644 index 00000000..8c318025 --- /dev/null +++ b/app/src/main/res/layout/item_with_details_horizontal.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 22fafcfd..3d60e89a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -231,5 +231,7 @@ Add widget Preferred sleep duration in hours An alarm was set for %1$02d:%2$02d + HW: %1$s + FW: %1$s