Răsfoiți Sursa

Collect Battery data

isundil 4 ani în urmă
părinte
comite
8ce201b661

+ 27 - 0
app/src/androidTest/java/info/knacki/prometheusandroidexporter/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package info.knacki.prometheusandroidexporter;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        assertEquals("info.knacki.prometheusandroidexporter", appContext.getPackageName());
+    }
+}

+ 14 - 3
app/src/main/AndroidManifest.xml

@@ -2,11 +2,14 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="info.knacki.prometheusandroidexporter">
     <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+
     <application
         android:allowBackup="true"
-        android:icon="@mipmap/ic_launcher"
+        android:icon="@mipmap/ic_launcher_foreground"
         android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
+        android:roundIcon="@mipmap/ic_launcher_foreground"
+        android:logo="@mipmap/ic_launcher_foreground"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
         <activity android:name=".MainActivity">
@@ -16,9 +19,17 @@
             </intent-filter>
         </activity>
 
+        <receiver android:name=".BootReceiver" android:enabled="true" android:exported="false">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+                <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
+            </intent-filter>
+        </receiver>
+
         <service
             android:name=".MainService"
             android:enabled="true"
-            android:exported="false" />
+            android:exported="false"/>
     </application>
 </manifest>

+ 12 - 0
app/src/main/java/info/knacki/prometheusandroidexporter/BootReceiver.java

@@ -0,0 +1,12 @@
+package info.knacki.prometheusandroidexporter;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class BootReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        MainService.StartService(context);
+    }
+}

+ 4 - 3
app/src/main/java/info/knacki/prometheusandroidexporter/CollectorManager.java

@@ -1,5 +1,7 @@
 package info.knacki.prometheusandroidexporter;
 
+import android.content.Context;
+
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
@@ -11,18 +13,17 @@ public class CollectorManager {
 
     private CollectorManager(){
         fCollectors = new HashMap<>();
-        tick();
     }
 
     public void RegisterCollector(ICollector collector) {
         fCollectors.put(collector.getClass().getName(), collector);
     }
 
-    public void tick() {
+    public void tick(Context ctx) {
         HashMap<String, Collection<CollectorValue>> values = new HashMap<>();
 
         for (HashMap.Entry<String, ICollector> i : fCollectors.entrySet()) {
-            values.put(i.getKey(), i.getValue().ReadValues());
+            values.put(i.getKey(), i.getValue().ReadValues(ctx));
         }
         fValues = values;
     }

+ 4 - 0
app/src/main/java/info/knacki/prometheusandroidexporter/CollectorValue.java

@@ -10,6 +10,7 @@ public class CollectorValue {
         public final static Type COUNTER = new Type("counter");
         public final static Type GAUGE = new Type("gauge");
         public final static Type SUMMARY = new Type("summary");
+        public final static Type STRING = new Type("string"); // FIXME legit ?
     }
 
     public final String fName;
@@ -36,6 +37,9 @@ public class CollectorValue {
     public void SetValue(double value) {
         valueAsString = Double.toString(value);
     }
+    public void SetValue(int value) {
+        valueAsString = Integer.toString(value);
+    }
 
     @NonNull
     @Override

+ 1 - 1
app/src/main/java/info/knacki/prometheusandroidexporter/CollectorWorker.java

@@ -25,7 +25,7 @@ public class CollectorWorker extends ListenableWorker {
     @NonNull
     @Override
     public ListenableFuture<Result> startWork() {
-        CollectorManager.GetInstance().tick();
+        CollectorManager.GetInstance().tick(getApplicationContext());
         return new ListenableFuture<Result>() {
             @Override
             public void addListener(Runnable listener, Executor executor) {

+ 3 - 1
app/src/main/java/info/knacki/prometheusandroidexporter/ICollector.java

@@ -1,7 +1,9 @@
 package info.knacki.prometheusandroidexporter;
 
+import android.content.Context;
+
 import java.util.Collection;
 
 public interface ICollector {
-    Collection<CollectorValue> ReadValues();
+    Collection<CollectorValue> ReadValues(Context ctx);
 }

+ 45 - 3
app/src/main/java/info/knacki/prometheusandroidexporter/MainActivity.java

@@ -2,17 +2,59 @@ package info.knacki.prometheusandroidexporter;
 
 import androidx.appcompat.app.AppCompatActivity;
 
+import android.app.ActivityManager;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.provider.SyncStateContract;
+import android.view.View;
+import android.widget.QuickContactBadge;
+import android.widget.TextView;
+
+import java.util.List;
+import java.util.logging.Logger;
 
 public class MainActivity extends AppCompatActivity {
+    private final static Logger log = Logger.getLogger(MainActivity.class.getName());
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
 
-        Intent i = new Intent(this, MainService.class);
-        startService(i);
+        findViewById(R.id.bt_killservice).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                MainService.StopService(MainActivity.this);
+                UpdateUiState();
+            }
+        });
+        findViewById(R.id.bt_startservice).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                MainService.StartService(MainActivity.this);
+                UpdateUiState();
+            }
+        });
+        UpdateUiState();
+    }
+
+    private boolean IsServiceRunning() {
+        final ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+        final List<ActivityManager.RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
+        final String serviceClassName = MainService.class.getName();
+
+        for (ActivityManager.RunningServiceInfo runningServiceInfo : services) {
+            if (runningServiceInfo.service.getClassName().equals(serviceClassName))
+                return true;
+        }
+        return false;
+    }
+
+    private void UpdateUiState() {
+        boolean running = IsServiceRunning();
+        findViewById(R.id.bt_killservice).setEnabled(running);
+        findViewById(R.id.bt_startservice).setEnabled(!running);
+        ((TextView) findViewById(R.id.txt_servicestate)).setText(running ? "running" : "stopped");
     }
-}
+}

+ 25 - 3
app/src/main/java/info/knacki/prometheusandroidexporter/MainService.java

@@ -4,6 +4,7 @@ import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
 
@@ -16,15 +17,33 @@ import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import info.knacki.prometheusandroidexporter.collector.TestCollector;
+import info.knacki.prometheusandroidexporter.collector.DeviceCollector;
+import info.knacki.prometheusandroidexporter.collector.PowerCollector;
 
 public class MainService extends Service {
     public MainService() {
     }
 
+    public static void StartService(Context ctx) {
+        Intent i = new Intent(ctx, MainService.class);
+        ctx.startService(i);
+    }
+
+    public static void StopService(Context ctx) {
+        Intent i = new Intent(ctx, MainService.class);
+        ctx.stopService(i);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+         return super.onStartCommand(intent, START_STICKY, startId);
+    }
+
     @Override
     public void onCreate() {
-        InitCollectors(CollectorManager.GetInstance());
+        CollectorManager collectorManager = CollectorManager.GetInstance();
+        collectorManager.tick(this);
+        InitCollectors(collectorManager);
         ScheduleCollection();
         try {
             HttpService.Register(getApplicationContext());
@@ -42,7 +61,9 @@ public class MainService extends Service {
     }
 
     private void InitCollectors(CollectorManager manager) {
-        manager.RegisterCollector(new TestCollector());
+        //manager.RegisterCollector(new TestCollector());
+        manager.RegisterCollector(new DeviceCollector());
+        manager.RegisterCollector(new PowerCollector());
     }
 
     private void ScheduleCollection() {
@@ -58,5 +79,6 @@ public class MainService extends Service {
             .setContentText("Service running")).build();
         notif.flags = Notification.FLAG_ONGOING_EVENT;
         ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).notify(1, notif);
+        startForeground(1, notif);
     }
 }

+ 33 - 0
app/src/main/java/info/knacki/prometheusandroidexporter/collector/DeviceCollector.java

@@ -0,0 +1,33 @@
+package info.knacki.prometheusandroidexporter.collector;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+
+import info.knacki.prometheusandroidexporter.CollectorValue;
+import info.knacki.prometheusandroidexporter.ICollector;
+
+public class DeviceCollector implements ICollector {
+    private final CollectorValue deviceUptimeCollector = new CollectorValue("device_uptime", "Device uptime (hours)", CollectorValue.Type.COUNTER);
+    private final double androidStartHours;
+
+    public DeviceCollector() {
+        androidStartHours = (System.currentTimeMillis() - SystemClock.uptimeMillis()) / (1000 * 60 * 60);
+    }
+
+    private CollectorValue Uptime() {
+        CollectorValue val = new CollectorValue(deviceUptimeCollector);
+        double currentHours = System.currentTimeMillis() / (1000. * 60 * 60);
+        val.SetValue((int) Math.round(currentHours - androidStartHours));
+        return val;
+    }
+
+    @Override
+    public Collection<CollectorValue> ReadValues(Context ctx) {
+        ArrayDeque<CollectorValue> result = new ArrayDeque<>();
+        result.add(Uptime());
+        return result;
+    }
+}

+ 132 - 0
app/src/main/java/info/knacki/prometheusandroidexporter/collector/PowerCollector.java

@@ -0,0 +1,132 @@
+package info.knacki.prometheusandroidexporter.collector;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+
+import info.knacki.prometheusandroidexporter.CollectorValue;
+import info.knacki.prometheusandroidexporter.ICollector;
+
+public class PowerCollector implements ICollector {
+    private final CollectorValue batteryLevelCollector = new CollectorValue("battery_level", "Battery level", CollectorValue.Type.COUNTER);
+    private final CollectorValue batteryStatusCollector = new CollectorValue("battery_status", "Battery level", CollectorValue.Type.STRING);
+    private final CollectorValue batteryPluggedCollector = new CollectorValue("battery_plugged", "Battery plugged", CollectorValue.Type.STRING);
+    private final CollectorValue batteryHealthCollector = new CollectorValue("battery_health", "Battery health", CollectorValue.Type.STRING);
+    private final CollectorValue batteryTemperatureCollector = new CollectorValue("battery_temp", "Battery temperature", CollectorValue.Type.COUNTER);
+    private final CollectorValue batteryVoltageCollector = new CollectorValue("battery_voltage", "Battery voltage (mV)", CollectorValue.Type.COUNTER);
+
+    private double GetBatteryLevel(Intent batteryStatus) {
+        double batteryMax = batteryStatus != null ? batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, 0) : 0;
+        double batteryLevel = batteryStatus != null ? batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) : -1;
+        if (batteryMax == 0 || batteryLevel == -1)
+            return -1;
+
+        return (100.*batteryLevel) / batteryMax;
+    }
+
+    private String GetBatteryStatus(Intent batteryStatus) {
+        int status = batteryStatus == null ? -1 : batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+        if (status == -1)
+            return "Error";
+        if (status == BatteryManager.BATTERY_STATUS_CHARGING)
+            return "Charging";
+        if (status == BatteryManager.BATTERY_STATUS_DISCHARGING)
+            return "Discharging";
+        if (status == BatteryManager.BATTERY_STATUS_FULL)
+            return "Full";
+        if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING)
+            return "Not charging";
+        return "Unknown";
+    }
+
+    private String GetBatteryPlugged(Intent batteryPlugged) {
+        int status = batteryPlugged == null ? -1 : batteryPlugged.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+        if (status == -1)
+            return "Error";
+        if (status == BatteryManager.BATTERY_PLUGGED_AC)
+            return "AC";
+        if (status == BatteryManager.BATTERY_PLUGGED_USB)
+            return "USB";
+        if (status == BatteryManager.BATTERY_PLUGGED_WIRELESS)
+            return "Wireless";
+        return "Unknown";
+    }
+
+    private String GetBatteryHealth(Intent batteryHealth) {
+        int health = batteryHealth == null ? -1 : batteryHealth.getIntExtra(BatteryManager.EXTRA_HEALTH, -1);
+        if (health == -1)
+            return "Error";
+        if (health == BatteryManager.BATTERY_HEALTH_GOOD)
+            return "Good";
+        if (health == BatteryManager.BATTERY_HEALTH_COLD)
+            return "Cold";
+        if (health == BatteryManager.BATTERY_HEALTH_DEAD)
+            return "Dead";
+        if (health == BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE)
+            return "Over Voltage";
+        if (health == BatteryManager.BATTERY_HEALTH_OVERHEAT)
+            return "Over Heat";
+        if (health == BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE)
+            return "Failure";
+        return "Unknown";
+    }
+
+    private CollectorValue BatteryLevel(Intent batteryStatus) {
+        CollectorValue val = new CollectorValue(batteryLevelCollector);
+        val.SetValue(GetBatteryLevel(batteryStatus));
+        return val;
+    }
+
+    private CollectorValue BatteryStatus(Intent batteryStatus) {
+        CollectorValue val = new CollectorValue(batteryStatusCollector);
+        val.SetValue(GetBatteryStatus(batteryStatus));
+        return val;
+    }
+
+    private CollectorValue BatteryPlugged(Intent batteryStatus) {
+        CollectorValue val = new CollectorValue(batteryPluggedCollector);
+        val.SetValue(GetBatteryPlugged(batteryStatus));
+        return val;
+    }
+
+    private CollectorValue BatteryHealth(Intent batteryStatus) {
+        CollectorValue val = new CollectorValue(batteryHealthCollector);
+        val.SetValue(GetBatteryHealth(batteryStatus));
+        return val;
+    }
+
+    private CollectorValue BatteryTemperature(Intent battery) {
+        CollectorValue val = new CollectorValue(batteryTemperatureCollector);
+        final int error = -100000;
+        int value = battery == null ? error : battery.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, error);
+        val.SetValue(value == error ? Double.NaN : ((double) value) / 10);
+        return val;
+    }
+
+    private CollectorValue BatteryVoltage(Intent battery) {
+        CollectorValue val = new CollectorValue(batteryVoltageCollector);
+        final int error = -100000;
+        int value = battery == null ? error : battery.getIntExtra(BatteryManager.EXTRA_VOLTAGE, error);
+        val.SetValue(value == error ? Double.NaN : ((double) value) / 10);
+        return val;
+    }
+
+    @Override
+    public Collection<CollectorValue> ReadValues(Context ctx) {
+        Intent batteryStatus = ctx.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        ArrayDeque<CollectorValue> result = new ArrayDeque<>();
+        result.add(BatteryLevel(batteryStatus));
+        result.add(BatteryStatus(batteryStatus));
+        result.add(BatteryPlugged(batteryStatus));
+        result.add(BatteryHealth(batteryStatus));
+        result.add(BatteryTemperature(batteryStatus));
+        result.add(BatteryVoltage(batteryStatus));
+        // TODO BatteryManager.computeChargeTimeRemaining Time before battery get fully charged, msec, API >= 28
+        return result;
+    }
+}

+ 15 - 1
app/src/main/java/info/knacki/prometheusandroidexporter/collector/TestCollector.java

@@ -1,5 +1,7 @@
 package info.knacki.prometheusandroidexporter.collector;
 
+import android.content.Context;
+
 import java.util.ArrayDeque;
 import java.util.Calendar;
 import java.util.Collection;
@@ -10,6 +12,8 @@ import info.knacki.prometheusandroidexporter.ICollector;
 public class TestCollector implements ICollector {
     private final CollectorValue countCollector = new CollectorValue("test_counter", "A count that counts", CollectorValue.Type.COUNTER);
     private final CollectorValue timerCollector = new CollectorValue("test_timer", "A clock that counts", CollectorValue.Type.COUNTER);
+    private final CollectorValue startedAtCollector = new CollectorValue("test_started", "A clock that counts", CollectorValue.Type.COUNTER);
+    private final long serviceStart = System.currentTimeMillis();
     private int count =0;
 
     private CollectorValue ReadCounter() {
@@ -26,11 +30,21 @@ public class TestCollector implements ICollector {
         return val;
     }
 
+    private CollectorValue ServiceStartedAt() {
+        CollectorValue val = new CollectorValue(startedAtCollector);
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(serviceStart);
+        int minutes = cal.get(Calendar.MINUTE);
+        val.SetValue(cal.get(Calendar.HOUR_OF_DAY) +(minutes < 10 ? "0" : "") +minutes);
+        return val;
+    }
+
     @Override
-    public Collection<CollectorValue> ReadValues() {
+    public Collection<CollectorValue> ReadValues(Context ctx) {
         ArrayDeque<CollectorValue> result = new ArrayDeque<>();
         result.add(ReadCounter());
         result.add(ReadTimer());
+        result.add(ServiceStartedAt());
         return result;
     }
 }

+ 51 - 0
app/src/main/res/layout/activity_main.xml

@@ -6,4 +6,55 @@
     android:layout_height="match_parent"
     tools:context=".MainActivity">
 
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        tools:layout_editor_absoluteX="36dp"
+        tools:layout_editor_absoluteY="106dp">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/textView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/service_is" />
+
+            <TextView
+                android:id="@+id/txt_servicestate"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <Space
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <Button
+                android:id="@+id/bt_killservice"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:enabled="false"
+                android:text="@string/kill" />
+
+            <Button
+                android:id="@+id/bt_startservice"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:enabled="false"
+                android:text="@string/start" />
+        </LinearLayout>
+
+        <Space
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -1,3 +1,6 @@
 <resources>
     <string name="app_name">Prometheus android exporter</string>
+    <string name="service_is">Service is</string>
+    <string name="kill">kill</string>
+    <string name="start">Start</string>
 </resources>

+ 17 - 0
app/src/test/java/info/knacki/prometheusandroidexporter/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package info.knacki.prometheusandroidexporter;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}