Android Open Source - batterylogger System State






From Project

Back to project page batterylogger.

License

The source code is released under:

Apache License

If you think the Android project batterylogger listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright 2014 Johan Walles <johan.walles@gmail.com>
 *//from w  w w . j  av a2 s.c o  m
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.gmail.walles.johan.batterylogger;

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.BatteryManager;
import android.os.Build;
import android.os.SystemClock;
import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.gmail.walles.johan.batterylogger.MainActivity.TAG;

/**
 * Snapshot of system state containing
 * <ul>
 *     <li>Installed applications</li>
 *     <li>Battery level</li>
 *     <li>Charging / not-charging status</li>
 *     <li>OS fingerprint</li>
 *     <li>When this system was last booted</li>
 * </ul>
 */
public class SystemState {

    private final Date timestamp;

    public int getBatteryPercentage() {
        return batteryPercentage;
    }

    private final int batteryPercentage;
    private final boolean charging;
    private final Date bootTimestamp;

    public Collection<InstalledApp> getInstalledApps() {
        return installedApps.values();
    }

    private final Map<String, InstalledApp> installedApps = new HashMap<String, InstalledApp>();

    public SystemState(Date timestamp, int batteryPercentage, boolean charging, Date bootTimestamp) {
        if (timestamp.before(bootTimestamp)) {
            throw new IllegalArgumentException("Sample timestamp must be after boot timestamp");
        }

        this.timestamp = timestamp;
        this.batteryPercentage = batteryPercentage;
        this.charging = charging;
        this.bootTimestamp = bootTimestamp;
    }

    public void addInstalledApp(String dottedName, String displayName, String versionName) {
        installedApps.put(dottedName, new InstalledApp(dottedName, displayName, versionName));
    }

    /**
     * Create amount dates between (but not including) t0 and t1.
     */
    static Date[] between(Date t0, Date t1, int amount) {
        Date returnMe[] = new Date[amount];
        long span = t1.getTime() - t0.getTime();
        for (int i = 0; i < amount; i++) {
            returnMe[i] = new Date(t0.getTime() + ((i + 1) * span) / (amount + 1));
        }
        return returnMe;
    }

    /**
     * @param events Packaging events will be added to this collection
     */
    private void addPackagingEventsSince(SystemState then, Collection<HistoryEvent> events) {
        Set<String> added = new HashSet<String>(installedApps.keySet());
        added.removeAll(then.installedApps.keySet());
        for (String dottedName : added) {
            InstalledApp installedApp = installedApps.get(dottedName);
            events.add(HistoryEvent.createInfoEvent(null,
                    installedApp.displayName + " " + installedApp.versionName + " installed"));
        }

        Set<String> removed = new HashSet<String>(then.installedApps.keySet());
        removed.removeAll(installedApps.keySet());
        for (String dottedName : removed) {
            InstalledApp installedApp = then.installedApps.get(dottedName);
            events.add(HistoryEvent.createInfoEvent(null,
                    installedApp.displayName + " " + installedApp.versionName + " uninstalled"));
        }

        Set<String> retained = new HashSet<String>(installedApps.keySet());
        retained.retainAll(then.installedApps.keySet());
        for (String dottedName : retained) {
            InstalledApp installedThen = then.installedApps.get(dottedName);
            InstalledApp installedNow = installedApps.get(dottedName);
            if (installedThen.equals(installedNow)) {
                continue;
            }
            events.add(HistoryEvent.createInfoEvent(null,
                    installedNow.displayName
                            + " upgraded from " + installedThen.versionName
                            + " to " + installedNow.versionName));
        }
    }

    public Collection<HistoryEvent> getEventsSince(SystemState then) {
        if (timestamp.before(then.timestamp)) {
            throw new IllegalArgumentException("Timestamp of other state must be older than mine");
        }

        List<HistoryEvent> returnMe = new LinkedList<HistoryEvent>();

        boolean reboot = false;
        if (!bootTimestampsMatch(then)) {
            returnMe.add(HistoryEvent.createSystemHaltingEvent(new Date(then.timestamp.getTime() + 1)));
            returnMe.add(HistoryEvent.createSystemBootingEvent(bootTimestamp, charging));
            reboot = true;
        }

        boolean discharging = !charging;
        boolean wasDischarging = !then.charging;
        if ((discharging || wasDischarging) && batteryPercentage != then.batteryPercentage) {
            returnMe.add(HistoryEvent.createBatteryLevelEvent(timestamp, batteryPercentage));
        }

        if (!reboot) {
            HistoryEvent chargingEvent = null;
            if (charging && !then.charging) {
                chargingEvent = HistoryEvent.createStartChargingEvent(null);
            }
            if (then.charging && !charging) {
                chargingEvent = HistoryEvent.createStopChargingEvent(null);
            }
            if (chargingEvent != null) {
                returnMe.add(chargingEvent);
            }
        }

        addPackagingEventsSince(then, returnMe);

        // Add dates to all events that need it
        int needTimestampCount = 0;
        for (HistoryEvent event : returnMe) {
            if (!event.isComplete()) {
                needTimestampCount++;
            }
        }
        if (needTimestampCount > 0) {
            Date timestamps[] = between(then.timestamp, timestamp, needTimestampCount);
            int nextFreeTimestampIndex = 0;
            for (HistoryEvent event : returnMe) {
                if (!event.isComplete()) {
                    event.setTimestamp(timestamps[nextFreeTimestampIndex++]);
                }
            }
        }

        // Now that everyone has their dates, sort!
        Collections.sort(returnMe);

        return returnMe;
    }

    @Override
    public int hashCode() {
        throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
        return "SystemState{" +
                "timestamp=" + timestamp +
                ", " + batteryPercentage + "%" +
                ", charging=" + charging +
                ", bootTimestamp=" + bootTimestamp +
                ", installedApps=" + installedApps +
                '}';
    }

    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    private boolean bootTimestampsMatch(SystemState that) {
        long bootMsDelta = Math.abs(this.bootTimestamp.getTime() - that.bootTimestamp.getTime());
        if (bootMsDelta > 20 * 1000) {
            // Boot time differs by more than 20s
            return false;
        }
        return true;
    }

    @Override
    public boolean equals(Object o) {
        if (!o.getClass().equals(SystemState.class)) {
            return false;
        }

        SystemState that = (SystemState)o;
        if (this.charging != that.charging) {
            return false;
        }
        if (this.batteryPercentage != that.batteryPercentage) {
            return false;
        }
        if (!this.timestamp.equals(that.timestamp)) {
            return false;
        }
        if (!bootTimestampsMatch(that)) {
            return false;
        }

        if (!this.installedApps.equals(that.installedApps)) {
            return false;
        }

        return true;
    }

    public void writeToFile(File file) throws IOException {
        File tmp = new File(file.getAbsolutePath() + ".tmp");

        PrintWriter writer = null;
        try {
            writer = new PrintWriter(new FileWriter(tmp));

            writer.println(timestamp.getTime());
            writer.println(batteryPercentage);
            writer.println(bootTimestamp.getTime());
            writer.println(charging);
            for (InstalledApp app : installedApps.values()) {
                app.println(writer);
            }
        } finally {
            if (writer != null) {
                writer.close();
            }
        }

        if (!tmp.renameTo(file)) {
            throw new IOException("Rename failed: " + tmp.getAbsolutePath() + "->" + file.getAbsolutePath());
        }
    }

    public static SystemState readFromFile(File file) throws IOException {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(file));
            Date timestamp = new Date(Long.valueOf(reader.readLine()));
            int batteryPercentage = Integer.valueOf(reader.readLine());
            Date bootTimestamp = new Date(Long.valueOf(reader.readLine()));
            boolean charging = Boolean.valueOf(reader.readLine());
            SystemState returnMe = new SystemState(timestamp, batteryPercentage, charging, bootTimestamp);

            while (true) {
                InstalledApp app = InstalledApp.readLines(reader);
                if (app == null) {
                    break;
                }

                returnMe.addInstalledApp(app.dottedName, app.displayName, app.versionName);
            }

            return returnMe;
        } catch (NumberFormatException e) {
            IOException throwMe = new IOException("Number parsing failed");
            throwMe.initCause(e);
            throw throwMe;
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
    }

    public static Date getBootTimestamp() {
        return new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime());
    }

    public static SystemState readFromSystem(Context context) throws IOException {
        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = context.registerReceiver(null, filter);
        if (batteryStatus == null) {
            throw new IOException("Battery status unavailable");
        }

        int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
        if (status == -1) {
            throw new IOException("Battery charging status unavailable");
        }
        boolean charging =
                (status == BatteryManager.BATTERY_STATUS_CHARGING
                        || status == BatteryManager.BATTERY_STATUS_FULL);

        int batteryLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        if (batteryLevel == -1) {
            throw new IOException("Battery level unavailable");
        }
        int batteryScale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        if (batteryScale == -1) {
            throw new IOException("Battery scale unavailable");
        }
        int batteryPercentage = (100 * batteryLevel) / batteryScale;

        SystemState returnMe = new SystemState(new Date(), batteryPercentage, charging, getBootTimestamp());

        // Add installed apps
        PackageManager packageManager = context.getPackageManager();
        if (packageManager == null) {
            throw new IOException("Package manager not available");
        }
        List<PackageInfo> installedPackages = packageManager.getInstalledPackages(0);
        for (PackageInfo packageInfo : installedPackages) {
            if (packageInfo.applicationInfo == null) {
                throw new IOException(
                        "Package info without application info: "
                                + packageInfo.packageName + " "
                                + packageInfo.versionName);
            }

            String dottedName = packageInfo.applicationInfo.packageName;
            if (dottedName == null) {
                throw new IOException("Dotted name unavailable for: "
                        + packageInfo.packageName + " "
                        + packageInfo.versionName);
            }

            CharSequence displayName = packageInfo.applicationInfo.loadLabel(packageManager);
            if (displayName == null) {
                displayName = dottedName;
            }

            String versionName = packageInfo.versionName;
            if (versionName == null || versionName.length() == 0) {
                versionName = Integer.toString(packageInfo.versionCode);
            }

            returnMe.addInstalledApp(
                    dottedName,
                    displayName.toString(),
                    versionName);
        }

        // Add OS fingerprint
        returnMe.addInstalledApp(
                "an.identifier.for.the.os.build.griseknoa",
                "Android OS",
                Build.FINGERPRINT);

        Log.v(TAG, String.format("System state sampled: %s, %d%%, %d apps",
                charging ? "charging" : "not charging",
                batteryPercentage,
                returnMe.getAppCount()));

        return returnMe;
    }

    public int getAppCount() {
        return installedApps.size();
    }
}




Java Source Code List

com.gmail.walles.johan.batterylogger.BatteryPlotFragment.java
com.gmail.walles.johan.batterylogger.DrainLinesCreatorTest.java
com.gmail.walles.johan.batterylogger.DrainLinesCreator.java
com.gmail.walles.johan.batterylogger.EventFormatter.java
com.gmail.walles.johan.batterylogger.EventReceiver.java
com.gmail.walles.johan.batterylogger.EventRenderer.java
com.gmail.walles.johan.batterylogger.EventSeries.java
com.gmail.walles.johan.batterylogger.HidableLineAndPointFormatter.java
com.gmail.walles.johan.batterylogger.HistoryEventTest.java
com.gmail.walles.johan.batterylogger.HistoryEvent.java
com.gmail.walles.johan.batterylogger.HistoryTest.java
com.gmail.walles.johan.batterylogger.History.java
com.gmail.walles.johan.batterylogger.InstalledAppTest.java
com.gmail.walles.johan.batterylogger.InstalledApp.java
com.gmail.walles.johan.batterylogger.MainActivity.java
com.gmail.walles.johan.batterylogger.RestartFormatter.java
com.gmail.walles.johan.batterylogger.SystemSamplingService.java
com.gmail.walles.johan.batterylogger.SystemStateTest.java
com.gmail.walles.johan.batterylogger.SystemState.java