diff --git a/res/layout/activity_device.xml b/res/layout/activity_device.xml
index 9f2f4cb1..a0505c05 100644
--- a/res/layout/activity_device.xml
+++ b/res/layout/activity_device.xml
@@ -1,126 +1,68 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/list_item_with_icon_entry.xml b/res/layout/list_item_with_icon_entry.xml
index 88a9d928..5eaef42c 100644
--- a/res/layout/list_item_with_icon_entry.xml
+++ b/res/layout/list_item_with_icon_entry.xml
@@ -3,6 +3,7 @@
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ tools:maxLength="20"
+ tools:text="@tools:sample/lorem/random"/>
+ android:visibility="gone"
+ tools:text="Other (optional) info"
+ tools:visibility="visible"/>
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/res/layout/view_pair_error.xml b/res/layout/view_pair_error.xml
new file mode 100644
index 00000000..ae8f603f
--- /dev/null
+++ b/res/layout/view_pair_error.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/view_pair_request.xml b/res/layout/view_pair_request.xml
new file mode 100644
index 00000000..46b1bf31
--- /dev/null
+++ b/res/layout/view_pair_request.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fac1dbb3..b1319352 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -135,6 +135,13 @@
Receive remote mouse movement
Mouse receiver
You need to enable Accessibility Service
+
+ Status
+ Battery: %d%%
+ Battery: %d%% Low Battery
+ Battery: %d%% charging
+ Battery information not available
+
Connected devices
Available devices
Remembered devices
diff --git a/src/org/kde/kdeconnect/Plugins/BatteryPlugin/BatteryPlugin.java b/src/org/kde/kdeconnect/Plugins/BatteryPlugin/BatteryPlugin.java
index b6db8f04..2c6f6935 100644
--- a/src/org/kde/kdeconnect/Plugins/BatteryPlugin/BatteryPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/BatteryPlugin/BatteryPlugin.java
@@ -11,6 +11,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
@@ -27,8 +29,15 @@ public class BatteryPlugin extends Plugin {
private static final int THRESHOLD_EVENT_NONE = 0;
private static final int THRESHOLD_EVENT_BATTERY_LOW = 1;
+ public static boolean isLowBattery(@NonNull DeviceBatteryInfo info) {
+ return info.getThresholdEvent() == THRESHOLD_EVENT_BATTERY_LOW;
+ }
+
private final NetworkPacket batteryInfo = new NetworkPacket(PACKET_TYPE_BATTERY);
+ @Nullable
+ private DeviceBatteryInfo remoteBatteryInfo;
+
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_battery);
@@ -73,6 +82,11 @@ public class BatteryPlugin extends Plugin {
intentFilter.addAction(Intent.ACTION_BATTERY_LOW);
Intent currentState = context.registerReceiver(receiver, intentFilter);
receiver.onReceive(context, currentState);
+
+ // Request new battery info from the linked device
+ NetworkPacket np = new NetworkPacket(PACKET_TYPE_BATTERY_REQUEST);
+ np.set("request", true);
+ device.sendPacket(np);
return true;
}
@@ -89,17 +103,36 @@ public class BatteryPlugin extends Plugin {
device.sendPacket(batteryInfo);
}
+ if (PACKET_TYPE_BATTERY.equals(np.getType())) {
+ remoteBatteryInfo = new DeviceBatteryInfo(np);
+ device.onPluginsChanged();
+ }
+
return true;
}
+ /**
+ * The latest battery information about the linked device. Will be null if the linked device
+ * has not sent us any such information yet.
+ *
+ * See {@link DeviceBatteryInfo} for info on which fields we expect to find.
+ *
+ *
+ * @return the most recent packet received from the remote device. May be null
+ */
+ @Nullable
+ public DeviceBatteryInfo getRemoteBatteryInfo() {
+ return remoteBatteryInfo;
+ }
+
@Override
public String[] getSupportedPacketTypes() {
- return new String[]{PACKET_TYPE_BATTERY_REQUEST};
+ return new String[]{PACKET_TYPE_BATTERY_REQUEST, PACKET_TYPE_BATTERY};
}
@Override
public String[] getOutgoingPacketTypes() {
- return new String[]{PACKET_TYPE_BATTERY};
+ return new String[]{PACKET_TYPE_BATTERY_REQUEST, PACKET_TYPE_BATTERY};
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/BatteryPlugin/DeviceBatteryInfo.kt b/src/org/kde/kdeconnect/Plugins/BatteryPlugin/DeviceBatteryInfo.kt
new file mode 100644
index 00000000..4d59e99f
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/BatteryPlugin/DeviceBatteryInfo.kt
@@ -0,0 +1,30 @@
+package org.kde.kdeconnect.Plugins.BatteryPlugin
+
+import org.kde.kdeconnect.NetworkPacket
+
+/**
+ * Specialised data representation of the packets received by [BatteryPlugin].
+ *
+ * Constants for [thresholdEvent] may be found in [BatteryPlugin].
+ *
+ * @param currentCharge the amount of charge in the device's battery
+ * @param isCharging whether the device is charging
+ * @param thresholdEvent status classifier (used to indicate low battery, etc.)
+ * @see BatteryPlugin.isLowBattery
+ */
+data class DeviceBatteryInfo(
+ val currentCharge: Int,
+ val isCharging: Boolean,
+ val thresholdEvent: Int,
+) {
+
+ /**
+ * For use with packets of type [BatteryPlugin.PACKET_TYPE_BATTERY].
+ */
+ constructor(np: NetworkPacket) :
+ this(
+ np.getInt("currentCharge"),
+ np.getBoolean("isCharging"),
+ np.getInt("thresholdEvent", 0)
+ )
+}
\ No newline at end of file
diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
index e28bf1e4..bbae3a35 100644
--- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
+++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
@@ -19,6 +19,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
@@ -29,6 +30,9 @@ import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Helpers.TelephonyHelper;
+import org.kde.kdeconnect.NetworkPacket;
+import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
+import org.kde.kdeconnect.Plugins.BatteryPlugin.DeviceBatteryInfo;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.SMSPlugin.SMSPlugin;
import org.kde.kdeconnect.UserInterface.List.FailedPluginListItem;
@@ -38,6 +42,8 @@ import org.kde.kdeconnect.UserInterface.List.PluginListHeaderItem;
import org.kde.kdeconnect.UserInterface.List.SetDefaultAppPluginListItem;
import org.kde.kdeconnect_tp.R;
import org.kde.kdeconnect_tp.databinding.ActivityDeviceBinding;
+import org.kde.kdeconnect_tp.databinding.ViewPairErrorBinding;
+import org.kde.kdeconnect_tp.databinding.ViewPairRequestBinding;
import java.util.ArrayList;
import java.util.Collection;
@@ -63,7 +69,24 @@ public class DeviceFragment extends Fragment {
private ArrayList pluginListItems;
- private ActivityDeviceBinding binding;
+ /**
+ * Top-level ViewBinding for this fragment.
+ *
+ * Host for {@link #pluginListItems}.
+ */
+ private ActivityDeviceBinding deviceBinding;
+ /**
+ * Not-yet-paired ViewBinding.
+ *
+ * Used to start and retry pairing.
+ */
+ private ViewPairRequestBinding binding;
+ /**
+ * Cannot-communicate ViewBinding.
+ *
+ * Used when the remote device is unreachable.
+ */
+ private ViewPairErrorBinding errorBinding;
public DeviceFragment() {
}
@@ -99,7 +122,12 @@ public class DeviceFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- binding = ActivityDeviceBinding.inflate(inflater, container, false);
+ deviceBinding = ActivityDeviceBinding.inflate(inflater, container, false);
+
+ // Inner binding for the layout shown when we're not paired yet...
+ binding = deviceBinding.pairRequest;
+ // ...and for when pairing doesn't (or can't) work
+ errorBinding = deviceBinding.pairError;
binding.pairButton.setOnClickListener(v -> {
binding.pairButton.setVisibility(View.GONE);
@@ -148,7 +176,7 @@ public class DeviceFragment extends Fragment {
});
- return binding.getRoot();
+ return deviceBinding.getRoot();
}
String getDeviceId() { return mDeviceId; }
@@ -272,8 +300,9 @@ public class DeviceFragment extends Fragment {
boolean reachable = device.isReachable();
binding.pairingButtons.setVisibility(paired ? View.GONE : View.VISIBLE);
- binding.errorMessageContainer.setVisibility((paired && !reachable) ? View.VISIBLE : View.GONE);
- binding.notReachableMessage.setVisibility((paired && !reachable) ? View.VISIBLE : View.GONE);
+ errorBinding.errorMessageContainer.setVisibility((paired && !reachable) ? View.VISIBLE : View.GONE);
+ errorBinding.notReachableMessage.setVisibility((paired && !reachable) ? View.VISIBLE : View.GONE);
+ deviceBinding.viewStatusContainer.setVisibility((paired && reachable) ? View.VISIBLE : View.GONE);
try {
pluginListItems = new ArrayList<>();
@@ -300,10 +329,12 @@ public class DeviceFragment extends Fragment {
dialog.show(getChildFragmentManager(), null);
}
});
+
+ DeviceFragment.this.displayBatteryInfoIfPossible();
}
ListAdapter adapter = new ListAdapter(mActivity, pluginListItems);
- binding.buttonsList.setAdapter(adapter);
+ deviceBinding.buttonsList.setAdapter(adapter);
mActivity.invalidateOptionsMenu();
@@ -373,4 +404,50 @@ public class DeviceFragment extends Fragment {
pluginListItems.add(new FailedPluginListItem(plugin, action));
}
}
+
+ /**
+ * This method tries to display battery info for the remote device. Includes
+ *
+ * - Current charge as a percentage
+ * - Whether the remote device is low on power
+ * - Whether the remote device is currently charging
+ *
+ *
+ * This will show a simple message on the view instead if we don't have
+ * accurate info right now.
+ *
+ */
+ private void displayBatteryInfoIfPossible() {
+ boolean canDisplayBatteryInfo = false;
+ BatteryPlugin batteryPlugin = (BatteryPlugin) device.getLoadedPlugins().get(
+ Plugin.getPluginKey(BatteryPlugin.class)
+ );
+
+ if (batteryPlugin != null) {
+ DeviceBatteryInfo info = batteryPlugin.getRemoteBatteryInfo();
+ if (info != null) {
+ canDisplayBatteryInfo = true;
+
+ Context ctx = deviceBinding.viewBatteryStatus.getContext();
+
+ boolean isCharging = info.isCharging();
+ @StringRes
+ int resId;
+ if (isCharging) {
+ resId = R.string.battery_status_charging_format;
+ } else if (BatteryPlugin.isLowBattery(info)) {
+ resId = R.string.battery_status_low_format;
+ } else {
+ resId = R.string.battery_status_format;
+ }
+
+ deviceBinding.viewBatteryStatus.setChecked(isCharging);
+ deviceBinding.viewBatteryStatus.setText(ctx.getString(resId, info.getCurrentCharge()));
+ }
+ }
+
+ if (!canDisplayBatteryInfo) {
+ deviceBinding.viewBatteryStatus.setText(R.string.battery_status_unknown);
+ }
+ }
}