mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
Changes from code review
This commit is contained in:
parent
1ccf15010e
commit
476304d6fb
@ -531,7 +531,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
@Override
|
@Override
|
||||||
public void onPacketReceived(@NonNull NetworkPacket np) {
|
public void onPacketReceived(@NonNull NetworkPacket np) {
|
||||||
|
|
||||||
PacketStats.countReceived(getDeviceId(), np.getType());
|
DeviceStats.countReceived(getDeviceId(), np.getType());
|
||||||
|
|
||||||
if (NetworkPacket.PACKET_TYPE_PAIR.equals(np.getType())) {
|
if (NetworkPacket.PACKET_TYPE_PAIR.equals(np.getType())) {
|
||||||
|
|
||||||
@ -694,7 +694,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
PacketStats.countSent(getDeviceId(), np.getType(), success);
|
DeviceStats.countSent(getDeviceId(), np.getType(), success);
|
||||||
if (success) break;
|
if (success) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -14,67 +15,74 @@ import java.util.Iterator;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class PacketStats {
|
public class DeviceStats {
|
||||||
|
|
||||||
private static final long EVENT_KEEP_WINDOW_MILLIS = 24 * 60 * 60 * 1000; // Keep 24 hours of events
|
/**
|
||||||
private static final long CLEANUP_INTERVAL_MILLIS = EVENT_KEEP_WINDOW_MILLIS/4; // Delete old (>24 hours) events every 6 hours
|
* Keep 24 hours of events
|
||||||
|
*/
|
||||||
|
private static final long EVENT_KEEP_WINDOW_MILLIS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
static class DeviceEvents {
|
/**
|
||||||
|
* Delete old (>24 hours, see EVENT_KEEP_WINDOW_MILLIS) events every 6 hours
|
||||||
|
*/
|
||||||
|
private static final long CLEANUP_INTERVAL_MILLIS = EVENT_KEEP_WINDOW_MILLIS/4;
|
||||||
|
|
||||||
|
private final static HashMap<String, PacketStats> eventsByDevice = new HashMap<>();
|
||||||
|
private static long nextCleanup = System.currentTimeMillis() + CLEANUP_INTERVAL_MILLIS;
|
||||||
|
|
||||||
|
static class PacketStats {
|
||||||
public long createdAtMillis = System.currentTimeMillis();
|
public long createdAtMillis = System.currentTimeMillis();
|
||||||
public HashMap<String, ArrayList<Long>> receivedByType = new HashMap<>();
|
public HashMap<String, ArrayList<Long>> receivedByType = new HashMap<>();
|
||||||
public HashMap<String, ArrayList<Long>> sentSuccessfulByType = new HashMap<>();
|
public HashMap<String, ArrayList<Long>> sentSuccessfulByType = new HashMap<>();
|
||||||
public HashMap<String, ArrayList<Long>> sentFailedByType = new HashMap<>();
|
public HashMap<String, ArrayList<Long>> sentFailedByType = new HashMap<>();
|
||||||
|
|
||||||
static class Counts {
|
static class Summary {
|
||||||
@NonNull String packetType;
|
final @NonNull String packetType;
|
||||||
int received = 0;
|
int received = 0;
|
||||||
int sentSuccessful = 0;
|
int sentSuccessful = 0;
|
||||||
int sentFailed = 0;
|
int sentFailed = 0;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
|
|
||||||
Counts(@NonNull String packetType) {
|
Summary(@NonNull String packetType) {
|
||||||
this.packetType = packetType;
|
this.packetType = packetType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public @NonNull Collection<Counts> getCounts() {
|
public @NonNull Collection<Summary> getSummaries() {
|
||||||
HashMap<String, Counts> countsByType = new HashMap<>();
|
HashMap<String, Summary> countsByType = new HashMap<>();
|
||||||
for (Map.Entry<String, ArrayList<Long>> entry : receivedByType.entrySet()) {
|
for (Map.Entry<String, ArrayList<Long>> entry : receivedByType.entrySet()) {
|
||||||
Counts counts = countsByType.computeIfAbsent(entry.getKey(), Counts::new);
|
Summary summary = countsByType.computeIfAbsent(entry.getKey(), Summary::new);
|
||||||
counts.received += entry.getValue().size();
|
summary.received += entry.getValue().size();
|
||||||
counts.total += entry.getValue().size();
|
summary.total += entry.getValue().size();
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, ArrayList<Long>> entry : sentSuccessfulByType.entrySet()) {
|
for (Map.Entry<String, ArrayList<Long>> entry : sentSuccessfulByType.entrySet()) {
|
||||||
Counts counts = countsByType.computeIfAbsent(entry.getKey(), Counts::new);
|
Summary summary = countsByType.computeIfAbsent(entry.getKey(), Summary::new);
|
||||||
counts.sentSuccessful += entry.getValue().size();
|
summary.sentSuccessful += entry.getValue().size();
|
||||||
counts.total += entry.getValue().size();
|
summary.total += entry.getValue().size();
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, ArrayList<Long>> entry : sentFailedByType.entrySet()) {
|
for (Map.Entry<String, ArrayList<Long>> entry : sentFailedByType.entrySet()) {
|
||||||
Counts counts = countsByType.computeIfAbsent(entry.getKey(), Counts::new);
|
Summary summary = countsByType.computeIfAbsent(entry.getKey(), Summary::new);
|
||||||
counts.sentFailed += entry.getValue().size();
|
summary.sentFailed += entry.getValue().size();
|
||||||
counts.total += entry.getValue().size();
|
summary.total += entry.getValue().size();
|
||||||
}
|
}
|
||||||
return countsByType.values();
|
return countsByType.values();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static HashMap<String, DeviceEvents> eventsByDevice = new HashMap<>();
|
|
||||||
private static long nextCleanup = System.currentTimeMillis() + CLEANUP_INTERVAL_MILLIS;
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public static @NonNull String getStatsForDevice(@NonNull String deviceId) {
|
public static @NonNull String getStatsForDevice(@NonNull String deviceId) {
|
||||||
|
|
||||||
cleanupIfNeeded();
|
cleanupIfNeeded();
|
||||||
|
|
||||||
DeviceEvents deviceEvents = eventsByDevice.get(deviceId);
|
PacketStats packetStats = eventsByDevice.get(deviceId);
|
||||||
if (deviceEvents == null) {
|
if (packetStats == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder ret = new StringBuilder();
|
StringBuilder ret = new StringBuilder();
|
||||||
|
|
||||||
long timeInMillis = System.currentTimeMillis() - deviceEvents.createdAtMillis;
|
long timeInMillis = System.currentTimeMillis() - packetStats.createdAtMillis;
|
||||||
if (timeInMillis > EVENT_KEEP_WINDOW_MILLIS) {
|
if (timeInMillis > EVENT_KEEP_WINDOW_MILLIS) {
|
||||||
timeInMillis = EVENT_KEEP_WINDOW_MILLIS;
|
timeInMillis = EVENT_KEEP_WINDOW_MILLIS;
|
||||||
}
|
}
|
||||||
@ -86,10 +94,10 @@ public class PacketStats {
|
|||||||
ret.append(minutes);
|
ret.append(minutes);
|
||||||
ret.append("m\n\n");
|
ret.append("m\n\n");
|
||||||
|
|
||||||
ArrayList<DeviceEvents.Counts> counts = new ArrayList<>(deviceEvents.getCounts());
|
ArrayList<PacketStats.Summary> counts = new ArrayList<>(packetStats.getSummaries());
|
||||||
Collections.sort(counts, (o1, o2) -> Integer.compare(o2.total, o1.total)); // Sort them by total number of events
|
Collections.sort(counts, (o1, o2) -> Integer.compare(o2.total, o1.total)); // Sort them by total number of events
|
||||||
|
|
||||||
for (DeviceEvents.Counts count : counts) {
|
for (PacketStats.Summary count : counts) {
|
||||||
String name = count.packetType;
|
String name = count.packetType;
|
||||||
if (name.startsWith("kdeconnect.")) {
|
if (name.startsWith("kdeconnect.")) {
|
||||||
name = name.substring("kdeconnect.".length());
|
name = name.substring("kdeconnect.".length());
|
||||||
@ -111,7 +119,13 @@ public class PacketStats {
|
|||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
|
||||||
return; // computeIfAbsent not present in API < 24
|
return; // computeIfAbsent not present in API < 24
|
||||||
}
|
}
|
||||||
eventsByDevice.computeIfAbsent(deviceId, key -> new DeviceEvents()).receivedByType.computeIfAbsent(packetType, key -> new ArrayList<>()).add(System.currentTimeMillis());
|
synchronized (DeviceStats.class) {
|
||||||
|
eventsByDevice
|
||||||
|
.computeIfAbsent(deviceId, key -> new PacketStats())
|
||||||
|
.receivedByType
|
||||||
|
.computeIfAbsent(packetType, key -> new ArrayList<>())
|
||||||
|
.add(System.currentTimeMillis());
|
||||||
|
}
|
||||||
cleanupIfNeeded();
|
cleanupIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,9 +134,21 @@ public class PacketStats {
|
|||||||
return; // computeIfAbsent not present in API < 24
|
return; // computeIfAbsent not present in API < 24
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
eventsByDevice.computeIfAbsent(deviceId, key -> new DeviceEvents()).sentSuccessfulByType.computeIfAbsent(packetType, key -> new ArrayList<>()).add(System.currentTimeMillis());
|
synchronized (DeviceStats.class) {
|
||||||
|
eventsByDevice
|
||||||
|
.computeIfAbsent(deviceId, key -> new PacketStats())
|
||||||
|
.sentSuccessfulByType
|
||||||
|
.computeIfAbsent(packetType, key -> new ArrayList<>())
|
||||||
|
.add(System.currentTimeMillis());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
eventsByDevice.computeIfAbsent(deviceId, key ->new DeviceEvents()).sentFailedByType.computeIfAbsent(packetType, key -> new ArrayList<>()).add(System.currentTimeMillis());
|
synchronized (DeviceStats.class) {
|
||||||
|
eventsByDevice
|
||||||
|
.computeIfAbsent(deviceId, key -> new PacketStats())
|
||||||
|
.sentFailedByType
|
||||||
|
.computeIfAbsent(packetType, key -> new ArrayList<>())
|
||||||
|
.add(System.currentTimeMillis());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cleanupIfNeeded();
|
cleanupIfNeeded();
|
||||||
}
|
}
|
||||||
@ -130,16 +156,19 @@ public class PacketStats {
|
|||||||
private static void cleanupIfNeeded() {
|
private static void cleanupIfNeeded() {
|
||||||
final long cutoutTimestamp = System.currentTimeMillis() - EVENT_KEEP_WINDOW_MILLIS;
|
final long cutoutTimestamp = System.currentTimeMillis() - EVENT_KEEP_WINDOW_MILLIS;
|
||||||
if (System.currentTimeMillis() > nextCleanup) {
|
if (System.currentTimeMillis() > nextCleanup) {
|
||||||
Log.i("PacketStats", "Doing periodic cleanup");
|
synchronized (DeviceStats.class) {
|
||||||
for (DeviceEvents de : eventsByDevice.values()) {
|
Log.i("PacketStats", "Doing periodic cleanup");
|
||||||
removeOldEvents(de.receivedByType, cutoutTimestamp);
|
for (PacketStats de : eventsByDevice.values()) {
|
||||||
removeOldEvents(de.sentFailedByType, cutoutTimestamp);
|
removeOldEvents(de.receivedByType, cutoutTimestamp);
|
||||||
removeOldEvents(de.sentSuccessfulByType, cutoutTimestamp);
|
removeOldEvents(de.sentFailedByType, cutoutTimestamp);
|
||||||
|
removeOldEvents(de.sentSuccessfulByType, cutoutTimestamp);
|
||||||
|
}
|
||||||
|
nextCleanup = System.currentTimeMillis() + CLEANUP_INTERVAL_MILLIS;
|
||||||
}
|
}
|
||||||
nextCleanup = System.currentTimeMillis() + CLEANUP_INTERVAL_MILLIS;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
static void removeOldEvents(HashMap<String, ArrayList<Long>> eventsByType, final long cutoutTimestamp) {
|
static void removeOldEvents(HashMap<String, ArrayList<Long>> eventsByType, final long cutoutTimestamp) {
|
||||||
|
|
||||||
Iterator<Map.Entry<String, ArrayList<Long>>> iterator = eventsByType.entrySet().iterator();
|
Iterator<Map.Entry<String, ArrayList<Long>>> iterator = eventsByType.entrySet().iterator();
|
||||||
@ -149,13 +178,13 @@ public class PacketStats {
|
|||||||
|
|
||||||
int index = Collections.binarySearch(events, cutoutTimestamp);
|
int index = Collections.binarySearch(events, cutoutTimestamp);
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
index = -(index + 1); // Convert the negative index to insertion point
|
index = -(index + 1); // Convert the negative index to insertion point
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < events.size()) {
|
if (index < events.size()) {
|
||||||
events.subList(0, index).clear();
|
events.subList(0, index).clear();
|
||||||
} else {
|
} else {
|
||||||
iterator.remove(); // No element greater than the threshold
|
iterator.remove(); // No element greater than the threshold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,7 +10,10 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
@ -18,8 +21,8 @@ import androidx.fragment.app.FragmentManager;
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.DeviceStats;
|
||||||
import org.kde.kdeconnect.KdeConnect;
|
import org.kde.kdeconnect.KdeConnect;
|
||||||
import org.kde.kdeconnect.PacketStats;
|
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect_tp.R;
|
import org.kde.kdeconnect_tp.R;
|
||||||
|
|
||||||
@ -101,12 +104,16 @@ public class PluginSettingsActivity
|
|||||||
return false; // PacketStats not working in API < 24
|
return false; // PacketStats not working in API < 24
|
||||||
}
|
}
|
||||||
menu.add(R.string.plugin_stats).setOnMenuItemClickListener(item -> {
|
menu.add(R.string.plugin_stats).setOnMenuItemClickListener(item -> {
|
||||||
String stats = PacketStats.getStatsForDevice(deviceId);
|
String stats = DeviceStats.getStatsForDevice(deviceId);
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(PluginSettingsActivity.this);
|
AlertDialog alertDialog = new MaterialAlertDialogBuilder(PluginSettingsActivity.this)
|
||||||
builder.setTitle(R.string.plugin_stats);
|
.setTitle(R.string.plugin_stats)
|
||||||
builder.setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
.setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss())
|
||||||
builder.setMessage(stats);
|
.setMessage(stats)
|
||||||
builder.show();
|
.show();
|
||||||
|
View messageView = alertDialog.findViewById(android.R.id.message);
|
||||||
|
if (messageView instanceof TextView) {
|
||||||
|
((TextView) messageView).setTextIsSelectable(true);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
@ -2,14 +2,11 @@ package org.kde.kdeconnect;
|
|||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
@RunWith(PowerMockRunner.class)
|
public class DeviceStatsTest {
|
||||||
public class PacketStatsTest {
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void removeOldEvents_cutoutExists() {
|
public void removeOldEvents_cutoutExists() {
|
||||||
@ -21,7 +18,7 @@ public class PacketStatsTest {
|
|||||||
events.add(20L);
|
events.add(20L);
|
||||||
events.add(30L);
|
events.add(30L);
|
||||||
final long cutout = 20L;
|
final long cutout = 20L;
|
||||||
PacketStats.removeOldEvents(eventsByType, cutout);
|
DeviceStats.removeOldEvents(eventsByType, cutout);
|
||||||
ArrayList<Long> eventsAfter = eventsByType.get(key);
|
ArrayList<Long> eventsAfter = eventsByType.get(key);
|
||||||
Assert.assertNotNull(eventsAfter);
|
Assert.assertNotNull(eventsAfter);
|
||||||
Assert.assertEquals(2, eventsAfter.size());
|
Assert.assertEquals(2, eventsAfter.size());
|
||||||
@ -39,7 +36,7 @@ public class PacketStatsTest {
|
|||||||
events.add(20L);
|
events.add(20L);
|
||||||
events.add(30L);
|
events.add(30L);
|
||||||
final long cutout = 25L;
|
final long cutout = 25L;
|
||||||
PacketStats.removeOldEvents(eventsByType, cutout);
|
DeviceStats.removeOldEvents(eventsByType, cutout);
|
||||||
ArrayList<Long> eventsAfter = eventsByType.get(key);
|
ArrayList<Long> eventsAfter = eventsByType.get(key);
|
||||||
Assert.assertNotNull(eventsAfter);
|
Assert.assertNotNull(eventsAfter);
|
||||||
Assert.assertEquals(1, eventsAfter.size());
|
Assert.assertEquals(1, eventsAfter.size());
|
||||||
@ -55,7 +52,7 @@ public class PacketStatsTest {
|
|||||||
events.add(10L);
|
events.add(10L);
|
||||||
events.add(20L);
|
events.add(20L);
|
||||||
final long cutout = 25L;
|
final long cutout = 25L;
|
||||||
PacketStats.removeOldEvents(eventsByType, cutout);
|
DeviceStats.removeOldEvents(eventsByType, cutout);
|
||||||
ArrayList<Long> eventsAfter = eventsByType.get(key);
|
ArrayList<Long> eventsAfter = eventsByType.get(key);
|
||||||
Assert.assertNull(eventsAfter);
|
Assert.assertNull(eventsAfter);
|
||||||
}
|
}
|
||||||
@ -68,7 +65,7 @@ public class PacketStatsTest {
|
|||||||
eventsByType.put(key, events);
|
eventsByType.put(key, events);
|
||||||
events.add(10L);
|
events.add(10L);
|
||||||
final long cutout = 5L;
|
final long cutout = 5L;
|
||||||
PacketStats.removeOldEvents(eventsByType, cutout);
|
DeviceStats.removeOldEvents(eventsByType, cutout);
|
||||||
ArrayList<Long> eventsAfter = eventsByType.get(key);
|
ArrayList<Long> eventsAfter = eventsByType.get(key);
|
||||||
Assert.assertNotNull(eventsAfter);
|
Assert.assertNotNull(eventsAfter);
|
||||||
Assert.assertEquals(1, eventsAfter.size());
|
Assert.assertEquals(1, eventsAfter.size());
|
Loading…
x
Reference in New Issue
Block a user