310 lines
10 KiB
Java
310 lines
10 KiB
Java
package com.eveningoutpost.dexdrip.Models;
|
|
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.os.BatteryManager;
|
|
import android.os.Build;
|
|
|
|
import com.eveningoutpost.dexdrip.GcmActivity;
|
|
import com.eveningoutpost.dexdrip.Home;
|
|
import com.eveningoutpost.dexdrip.UtilityModels.BridgeBattery;
|
|
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
|
import com.eveningoutpost.dexdrip.UtilityModels.StatusItem;
|
|
import com.eveningoutpost.dexdrip.UtilityModels.desertsync.RouteTools;
|
|
import com.eveningoutpost.dexdrip.utils.CipherUtils;
|
|
import com.eveningoutpost.dexdrip.xdrip;
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.GsonBuilder;
|
|
import com.google.gson.annotations.Expose;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import static com.eveningoutpost.dexdrip.Models.JoH.emptyString;
|
|
|
|
/**
|
|
* Created by jamorham on 20/01/2017.
|
|
*/
|
|
|
|
public class RollCall {
|
|
|
|
private static final String TAG = "RollCall";
|
|
private static final int MAX_SSID_LENGTH = 20;
|
|
private static volatile HashMap<String, RollCall> indexed;
|
|
|
|
@Expose
|
|
String device_manufactuer;
|
|
@Expose
|
|
String device_model;
|
|
@Expose
|
|
String device_serial;
|
|
@Expose
|
|
String device_name;
|
|
@Expose
|
|
String android_version;
|
|
@Expose
|
|
String xdrip_version;
|
|
@Expose
|
|
String role;
|
|
@Expose
|
|
String ssid;
|
|
@Expose
|
|
String mhint;
|
|
@Expose
|
|
int battery = -1;
|
|
@Expose
|
|
int bridge_battery = -1;
|
|
|
|
// not set by instantiation
|
|
@Expose
|
|
String hash;
|
|
@Expose
|
|
Long last_seen;
|
|
|
|
final long created = JoH.tsl();
|
|
|
|
public RollCall() {
|
|
this.device_manufactuer = Build.MANUFACTURER;
|
|
this.device_model = Build.MODEL;
|
|
this.device_name = JoH.getLocalBluetoothName(); // sanity check length
|
|
this.device_serial = Build.SERIAL;
|
|
this.android_version = Build.VERSION.RELEASE;
|
|
this.xdrip_version = JoH.getVersionDetails();
|
|
|
|
if (Home.get_follower()) {
|
|
this.role = "Follower";
|
|
} else if (Home.get_master()) {
|
|
this.role = "Master";
|
|
} else {
|
|
this.role = "None";
|
|
}
|
|
|
|
if (DesertSync.isEnabled()) {
|
|
try {
|
|
this.ssid = wifiString();
|
|
} catch (Exception e) {
|
|
//
|
|
}
|
|
try {
|
|
if (this.role.equals("Master")) {
|
|
this.mhint = RouteTools.getBestInterfaceAddress();
|
|
}
|
|
} catch (Exception e) {
|
|
//
|
|
}
|
|
}
|
|
}
|
|
|
|
// populate with values from this device
|
|
public RollCall populate() {
|
|
this.battery = getBatteryLevel();
|
|
this.bridge_battery = BridgeBattery.getBestBridgeBattery();
|
|
return this;
|
|
}
|
|
|
|
private boolean batteryValid() {
|
|
return battery != -1;
|
|
}
|
|
|
|
private boolean bridgeBatteryValid() {
|
|
return bridge_battery > 0;
|
|
}
|
|
|
|
private static String wifiString() {
|
|
String ssid = JoH.getWifiSSID();
|
|
if (ssid != null && ssid.length() > MAX_SSID_LENGTH) {
|
|
ssid = ssid.substring(0, 20);
|
|
}
|
|
return ssid;
|
|
}
|
|
|
|
@SuppressWarnings("ConstantConditions")
|
|
private static int getBatteryLevel() {
|
|
final Intent batteryIntent = xdrip.getAppContext().registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
|
try {
|
|
final int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
|
final int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
|
|
if (level == -1 || scale == -1) {
|
|
return -1;
|
|
}
|
|
return (int) (((float) level / (float) scale) * 100.0f);
|
|
} catch (NullPointerException e) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
private String getRemoteIpStatus() {
|
|
if (mhint != null) {
|
|
return "\n" + mhint;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
private String getRemoteWifiIndicate(final String our_wifi_ssid) {
|
|
if (emptyString(our_wifi_ssid)) return "";
|
|
if (emptyString(ssid)) return "";
|
|
if (!our_wifi_ssid.equals(ssid)) return "\n" + ssid;
|
|
return "";
|
|
}
|
|
|
|
public String toS() {
|
|
final Gson gson = new GsonBuilder()
|
|
.excludeFieldsWithoutExposeAnnotation()
|
|
.create();
|
|
return gson.toJson(this);
|
|
}
|
|
|
|
public String getHash() {
|
|
if (this.hash == null) {
|
|
this.hash = CipherUtils.getSHA256(this.device_manufactuer + this.android_version + this.device_model + this.device_serial);
|
|
}
|
|
return this.hash;
|
|
}
|
|
|
|
public String bestName() {
|
|
if ((device_name != null) && (device_name.length() > 2)) {
|
|
return device_name;
|
|
}
|
|
return (!device_manufactuer.equals("unknown") ? device_manufactuer + " " : "") + device_model;
|
|
}
|
|
|
|
public static RollCall fromJson(String json) {
|
|
final Gson gson = new GsonBuilder()
|
|
.excludeFieldsWithoutExposeAnnotation()
|
|
.create();
|
|
try {
|
|
return gson.fromJson(json, RollCall.class);
|
|
} catch (Exception e) {
|
|
UserError.Log.e(TAG, "Got exception processing fromJson() " + e);
|
|
UserError.Log.e(TAG, "json = " + json);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public synchronized static void Seen(String item_json) {
|
|
try {
|
|
UserError.Log.d(TAG, "Processing Seen: " + item_json);
|
|
Seen(fromJson(item_json));
|
|
} catch (Exception e) {
|
|
UserError.Log.e(TAG, "Got exception processing Seen() " + e);
|
|
}
|
|
}
|
|
|
|
private synchronized static void Seen(RollCall item) {
|
|
// sanity check object contains some data
|
|
if (item == null) return;
|
|
if ((item.android_version == null) || (item.android_version.length() == 0)) return;
|
|
if (indexed == null) loadIndex();
|
|
indexed.put(item.getHash(), item);
|
|
item.last_seen = JoH.tsl();
|
|
saveIndex();
|
|
}
|
|
|
|
private static final String ROLLCALL_SAVED_INDEX = "RollCall-saved-index";
|
|
|
|
private static void saveIndex() {
|
|
final Gson gson = new GsonBuilder().create();
|
|
final String[] array = new String[indexed.size()];
|
|
int i = 0;
|
|
for (Map.Entry entry : indexed.entrySet()) {
|
|
array[i++] = (((RollCall) entry.getValue()).toS());
|
|
}
|
|
PersistentStore.setString(ROLLCALL_SAVED_INDEX, gson.toJson(array));
|
|
UserError.Log.d(TAG, "Saving");
|
|
}
|
|
|
|
private synchronized static void loadIndex() {
|
|
UserError.Log.d(TAG, "Loading index");
|
|
final String loaded = PersistentStore.getString(ROLLCALL_SAVED_INDEX);
|
|
final HashMap<String, RollCall> hashmap = new HashMap<>();
|
|
try {
|
|
if ((loaded != null) && (loaded.length() > 0)) {
|
|
final Gson gson = new GsonBuilder().create();
|
|
final String[] array = gson.fromJson(loaded, String[].class);
|
|
if (array != null) {
|
|
for (String json : array) {
|
|
RollCall item = gson.fromJson(json, RollCall.class);
|
|
hashmap.put(item.getHash(), item);
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
UserError.Log.e(TAG, "Error loading index: " + e);
|
|
}
|
|
indexed = hashmap;
|
|
UserError.Log.d(TAG, "Loaded: count: " + hashmap.size());
|
|
}
|
|
|
|
public static String getBestMasterHintIP() {
|
|
// TODO some intelligence regarding wifi ssid
|
|
//final String our_wifi_ssid = wifiString();
|
|
if (indexed == null) loadIndex();
|
|
RollCall bestMatch = null;
|
|
for (Map.Entry entry : indexed.entrySet()) {
|
|
final RollCall rc = (RollCall) entry.getValue();
|
|
if (!rc.role.equals("Master")) continue;
|
|
if (emptyString(rc.mhint)) continue;
|
|
if (bestMatch == null || rc.last_seen > bestMatch.last_seen) {
|
|
bestMatch = rc;
|
|
}
|
|
}
|
|
UserError.Log.d(TAG, "Returning best master hint ip: " + (bestMatch != null ? bestMatch.toS() : "no match"));
|
|
return bestMatch != null ? bestMatch.mhint : null;
|
|
}
|
|
|
|
|
|
public static void pruneOld(int depth) {
|
|
if (indexed == null) loadIndex();
|
|
if (depth > 10) return;
|
|
boolean changed = false;
|
|
for (Map.Entry entry : indexed.entrySet()) {
|
|
RollCall rc = (RollCall) entry.getValue();
|
|
long since = JoH.msSince(rc.last_seen);
|
|
|
|
if ((since < 0) || (since > (1000 * 60 * 60 * 24))) {
|
|
UserError.Log.d(TAG, "Pruning entry: " + rc.bestName());
|
|
indexed.remove(entry.getKey().toString());
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (changed) {
|
|
saveIndex();
|
|
pruneOld(depth + 1);
|
|
}
|
|
}
|
|
|
|
// data for MegaStatus
|
|
public static List<StatusItem> megaStatus() {
|
|
if (indexed == null) loadIndex();
|
|
GcmActivity.requestRollCall();
|
|
// TODO sort data
|
|
final boolean engineering = Home.get_engineering_mode();
|
|
final boolean desert_sync = DesertSync.isEnabled();
|
|
final String our_wifi_ssid = desert_sync ? wifiString() : "";
|
|
final List<StatusItem> lf = new ArrayList<>();
|
|
for (Map.Entry entry : indexed.entrySet()) {
|
|
final RollCall rc = (RollCall) entry.getValue();
|
|
// TODO refactor with stringbuilder
|
|
lf.add(new StatusItem(rc.role + (desert_sync ? rc.getRemoteWifiIndicate(our_wifi_ssid) : "") + (engineering ? ("\n" + JoH.niceTimeSince(rc.last_seen) + " ago") : ""), rc.bestName() + (desert_sync ? rc.getRemoteIpStatus() : "") + (engineering && rc.batteryValid() ? ("\n" + rc.battery + "%") : "") + (engineering && rc.bridgeBatteryValid() ? (" " + rc.bridge_battery+"%") : "")));
|
|
}
|
|
|
|
Collections.sort(lf, new Comparator<StatusItem>() {
|
|
public int compare(StatusItem left, StatusItem right) {
|
|
int val = right.name.replaceFirst("\n.*$", "").compareTo(left.name.replaceFirst("\n.*$", "")); // descending sort ignore second line
|
|
if (val == 0) val = left.value.compareTo(right.value); // ascending sort
|
|
return val;
|
|
}
|
|
});
|
|
// TODO could scan for duplicates and append serial to bestName
|
|
|
|
return new ArrayList<>(lf);
|
|
}
|
|
|
|
}
|