Android Open Source - powerstats Power Stats Plot






From Project

Back to project page powerstats.

License

The source code is released under:

GNU General Public License

If you think the Android project powerstats 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

package com.znasibov.powerstats;
//from  w  ww .j  a  v  a 2  s  .  co  m
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Shader;
import android.os.Handler;
import android.text.format.DateFormat;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;

import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.FillDirection;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYStepMode;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;


public class PowerStatsPlot extends XYPlot implements View.OnTouchListener {
    private static final int DOMAIN_STEPS_COUNT = 5;
    private static final long MIN_DOMAIN_BOUNDARIES_LENGTH = Util.hoursToMs(1);

    private float leftBoundary = 0f;
    private float rightBoundary = 0f;
    private float leftBound = 0f;
    private float rightBound = 0f;
    private long stepLength = 0;

    private ArrayList<StatRenderer> statRenderers = new ArrayList<StatRenderer>();

    public PowerStatsPlot(android.content.Context context, android.util.AttributeSet attrs) {
        super(context, attrs);
        initStatRenderers();
        initPlot();
        initTouchHandling();
    }

    private void initPlot() {
        setRangeBoundaries(getLowerBoundary(), getUpperBoundary(), BoundaryMode.FIXED);
        setUserRangeOrigin(0);
        setRangeStep(XYStepMode.INCREMENT_BY_VAL, 10);
        setDomainStep(XYStepMode.SUBDIVIDE, DOMAIN_STEPS_COUNT);

        setDomainValueFormat(new Format() {
            String timePattern; {
                if (DateFormat.is24HourFormat(getContext())) {
                    timePattern = "HH:mm";
                } else {
                    timePattern = "hh:mm a";
                }
            }

            String datePattern; {
                // Screw the US date format for this!
                if (getContext().getResources().getConfiguration().locale == Locale.US) {
                    datePattern = "M/d";
                } else {
                    datePattern = "d/M";
                }
            }

            String dateTimePattern = timePattern + " " + datePattern;

            DateTimeFormatter domainTimeFormatter = DateTimeFormat.forPattern(timePattern);
            DateTimeFormatter domainDateFormatter = DateTimeFormat.forPattern(datePattern);
            DateTimeFormatter domainDateTimeFormatter = DateTimeFormat.forPattern(dateTimePattern);

            @Override
            public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
                long timestamp = ((Number) obj).longValue();
                if (timestamp <= 0) {
                    return toAppendTo;
                }

                DateTime ts = new DateTime(timestamp);
                long daystamp = ts.dayOfWeek().roundHalfEvenCopy().getMillis();

                long stampsDiff = timestamp - daystamp;
                boolean longDomainSpan = rightBound - leftBound > Util.daysToMs(1);
                boolean timestampNearDaySplit = timestamp >= daystamp && stampsDiff <= stepLength;

                if (longDomainSpan) {
                    toAppendTo.append(domainDateFormatter.print(ts));
                } else if (timestampNearDaySplit) {
                    toAppendTo.append(domainDateTimeFormatter.print(ts));
                } else {
                    toAppendTo.append(domainTimeFormatter.print(ts));
                }

                return toAppendTo;
            }

            @Override
            public Object parseObject(String source, ParsePosition pos) {
                return null;
            }
        });

        setRangeValueFormat(new Format() {
            int rendererIndex = 0;
            HashMap<Float, StatRenderer> valueToRendererMappings = new HashMap<Float, StatRenderer>();

            @Override
            public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition fieldPosition) {
                float value = ((Number)obj).floatValue();

                if (value < 0 && value % 10 == -0.0f) { // -10, -20, ...
                    if (valueToRendererMappings.size() < statRenderers.size()) {
                        valueToRendererMappings.put(value, statRenderers.get(rendererIndex));
                        rendererIndex++;
                    }
                    StatRenderer renderer = valueToRendererMappings.get(value);
                    toAppendTo.append(renderer.getLabel());
                }
                else {
                    toAppendTo.append(obj);
                }

                return toAppendTo;
            }

            @Override
            public Object parseObject(String s, ParsePosition parsePosition) {
                return null;
            }
        });
    }

    private int getUpperBoundary() {
        return 105;
    }

    private int getLowerBoundary() {
        return -5 + -10 * statRenderers.size();
    }

    private void initStatRenderers() {
        statRenderers.add(new PhoneServiceRenderer(this));
        statRenderers.add(new WifiRenderer(this));
        statRenderers.add(new MobileDataRenderer(this));
        statRenderers.add(new GpsRenderer(this));
        statRenderers.add(new ScreenStateRenderer(this));

        for (int i = 0; i < statRenderers.size(); i++) {
            float rangeValue = -2 + -10 * (i + 1);
            statRenderers.get(i).setRangeValue(rangeValue);
        }
    }

    private void initTouchHandling()
    {
        this.setOnTouchListener(this);
    }

    public void render(ArrayList<PowerRecord> records) {
        if (records.size() == 0) {
            return;
        }
        clear();

        rightBoundary = records.get(records.size() - 1).getTimestamp();
        leftBoundary = records.get(0).getTimestamp();

        float rightBound = records.get(records.size() - 1).getTimestamp();
        float leftBound = rightBound - UserPreferences.getPowerStatsPlotDefaultDomainSize();
        setDomainBoundaries(leftBound, rightBound);

        renderDaySeparators(records);
        renderBatteryLevelPlot(records);
        renderBatteryCharging(records);

        for (StatRenderer st: statRenderers) {
            st.render(records);
        }
        redraw();
    }

    private void renderDaySeparators(ArrayList<PowerRecord> records) {
        // VISUAL
        LineAndPointFormatter formatter = new LineAndPointFormatter(
                getResources().getColor(R.color.plot_day_separator), null, null, null);

        Paint linePaint = formatter.getLinePaint();
        linePaint.setStrokeWidth(getResources().getDimension(R.dimen.plot_bg_line_width));
        linePaint.setPathEffect(new DashPathEffect(new float[]{10.0f, 5.0f}, 0));

        // DATA
        long firstTimestamp = records.get(0).getTimestamp();
        long lastTimestamp = records.get(records.size() - 1).getTimestamp();

        DateTime ts = new DateTime(firstTimestamp);
        long daystamp = ts.dayOfWeek().roundHalfEvenCopy().getMillis();
        long dayAsMillis = Util.daysToMs(1);

        ArrayList<Number> values = new ArrayList<Number>();
        for (long t = daystamp; t < lastTimestamp; t += dayAsMillis) {
            values.add(t);
            values.add(getUpperBoundary());
            values.add(t);
            values.add(getLowerBoundary());
            values.add(t + 1);
            values.add(null);
        }

        SimpleXYSeries series = new SimpleXYSeries("Day");
        series.setModel(values, SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED);
        addSeries(series, formatter);
    }

    private void renderBatteryLevelPlot(ArrayList<PowerRecord> records) {
        // VISUAL
        LineAndPointFormatter formatter = new LineAndPointFormatter(
                Color.rgb(128, 255, 128), null, null, null);

        Paint linePaint = formatter.getLinePaint();
        linePaint.setStrokeWidth(getResources().getDimension(R.dimen.plot_line_width));
        linePaint.setPathEffect(new CornerPathEffect(10));

        // DATA
        ArrayList<Number> values = new ArrayList<Number>();
        for (PowerRecord r: records) {
            values.add(r.getTimestamp());
            values.add(r.getBatteryValue());
        }

        SimpleXYSeries series = new SimpleXYSeries("Battery level");
        series.setModel(values, SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED);
        addSeries(series, formatter);
    }

    private void renderBatteryCharging(ArrayList<PowerRecord> records) {
        // VISUAL
        LineAndPointFormatter formatter = new LineAndPointFormatter(
                Color.TRANSPARENT, null, null, null);

        Paint fillPaint = new Paint();
        fillPaint.setAlpha(100);

        int gradientHeight = getHeight();

        fillPaint.setShader(new LinearGradient(0, 0, 0, gradientHeight, Color.WHITE, Color.GREEN, Shader.TileMode.MIRROR));
        formatter.setFillPaint(fillPaint);
        formatter.setFillDirection(FillDirection.RANGE_ORIGIN);

        // DATA
        ArrayList<Number> values = new ArrayList<Number>();
        for (PowerRecord r: records) {
            values.add(r.getTimestamp());
            if (r.getBatteryStatus() == PowerRecord.BATTERY_STATUS_CHARGING) {
                values.add(r.getBatteryValue());
            } else {
                values.add(null);
            }
        }

        SimpleXYSeries series = new SimpleXYSeries("Battery charging");
        series.setModel(values, SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED);
        addSeries(series, formatter);
    }



    // ***** TOUCH EVENTS AND SCROLLING ******
    // ***************************************

    // finger states
    PointF firstFinger;
    float distBetweenFingers;

    // Definition of the touch states
    static final int TOUCH_NONE = 0;
    static final int TOUCH_ONE_FINGER_DRAG = 1;
    static final int TOUCH_TWO_FINGERS_DRAG = 2;

    int touchMode = TOUCH_NONE;

    // autoscroll
    static final float MINIMAL_VELOCITY_FOR_AUTOSCROLL_START = 300.0f;
    static final int AUTOSCROLL_PERIOD_MS = 1000 / 25; // try 25 FPS, very hard to achieve :(
    static final int VELOCITY_PERIOD_MS = 1000;
    static final float MINIMAL_AUTOSCROLL_VELOCITY = 100.0f;
    VelocityTracker moveVelocity = VelocityTracker.obtain();
    boolean autoScrolling = false;
    Handler autoScrollHandler = new Handler();


    @Override
    public boolean onTouch(View view, MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: // Start gesture
                firstFinger = new PointF(event.getX(), event.getY());
                touchMode = TOUCH_ONE_FINGER_DRAG;
                moveVelocity.clear();
                autoScrolling = false;
                break;
            case MotionEvent.ACTION_POINTER_UP:
                touchMode = TOUCH_NONE;
                break;
            case MotionEvent.ACTION_POINTER_DOWN: // second finger
                distBetweenFingers = getDistanceBetweenFingers(event);
                // the distance check is done to avoid false alarms
                if (distBetweenFingers > 5) {
                    touchMode = TOUCH_TWO_FINGERS_DRAG;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (touchMode == TOUCH_ONE_FINGER_DRAG) {
                    PointF oldFirstFinger = firstFinger;
                    firstFinger = new PointF(event.getX(), event.getY());
                    scroll(oldFirstFinger.x - firstFinger.x);
                    moveVelocity.addMovement(event);
                } else if (touchMode == TOUCH_TWO_FINGERS_DRAG) {
                    float oldDist = distBetweenFingers;
                    distBetweenFingers = getDistanceBetweenFingers(event);
                    zoom(oldDist / distBetweenFingers);
                }
                break;
            case MotionEvent.ACTION_UP:
                touchMode = TOUCH_NONE;
                moveVelocity.computeCurrentVelocity(VELOCITY_PERIOD_MS);

                if (UserPreferences.getPowerStatsPlotSmoothScrollingEnabled()) {
                    if (Math.abs(moveVelocity.getXVelocity()) > MINIMAL_VELOCITY_FOR_AUTOSCROLL_START) {
                        startAutoScroll(moveVelocity.getXVelocity());
                    }
                }
                break;

        }
        return true;
    }

    private float getDistanceBetweenFingers(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return FloatMath.sqrt(x * x + y * y);
    }

    private void zoom(float scale) {
        float domainSpan = rightBound - leftBound;
        float domainMidPoint = rightBound - domainSpan / 2.0f;
        float offset = domainSpan * scale / 2.0f;
        float newLeftBound = domainMidPoint - offset;
        float newRightBound = domainMidPoint + offset;

        // restrict zooming further
        if (newRightBound - newLeftBound < MIN_DOMAIN_BOUNDARIES_LENGTH) {
            return;
        }

        if (newLeftBound < leftBoundary) {
            newLeftBound = leftBoundary;
        }
        if (newRightBound > rightBoundary) {
            newRightBound = rightBoundary;
        }

        setDomainBoundaries(newLeftBound, newRightBound);
        redraw();
    }

    private void scroll(float pan) {
        float domainSpan = rightBound - leftBound;
        float step = domainSpan / getWidth();
        float offset = pan * step;

        float newLeftBound = leftBound + offset;
        float newRightBound = rightBound + offset;

        if (newLeftBound < leftBoundary) {
            newLeftBound = leftBoundary;
            newRightBound = newLeftBound + domainSpan;
        } else if (newRightBound > rightBoundary) {
            newRightBound = rightBoundary;
            newLeftBound = rightBoundary - domainSpan;
        }
        setDomainBoundaries(newLeftBound, newRightBound);
        redraw();
    }

    private void startAutoScroll(final float velocity) {
        autoScrolling = true;

        autoScrollHandler.postDelayed(new Runnable() {
            final float relativeVelocity = velocity * (rightBound - leftBound) / (1000 * getWidth());
            float autoScrollVelocity = 0.01f * relativeVelocity;
            float leftBoundBeforeScroll = leftBound;
            float rightBoundBeforeScroll = rightBound;

            float AUTOSCROLL_SLOWDOWN_FACTOR = 0.8f;

            @Override
            public void run() {
                if (!autoScrolling) {
                    return;
                }

                autoScrollVelocity *= AUTOSCROLL_SLOWDOWN_FACTOR;
                if (Math.abs(autoScrollVelocity) > MINIMAL_AUTOSCROLL_VELOCITY) {
                    scroll(-autoScrollVelocity);
                }

                if (leftBoundBeforeScroll != leftBound || rightBoundBeforeScroll != rightBound) {
                    leftBoundBeforeScroll = leftBound;
                    rightBoundBeforeScroll = rightBound;
                    autoScrollHandler.postDelayed(this, AUTOSCROLL_PERIOD_MS);
                }
            }
        }, 0);


    }

    private void setDomainBoundaries(float leftBound, float rightBound) {
        this.leftBound = leftBound;
        this.rightBound = rightBound;
        stepLength = (long)((rightBound - leftBound) / (DOMAIN_STEPS_COUNT - 1));
        super.setDomainBoundaries(leftBound, rightBound, BoundaryMode.FIXED);
    }

}

abstract class StatRenderer {
    PowerStatsPlot plot;
    float rangeValue = 0;

    public StatRenderer(PowerStatsPlot plot) {
        this.plot = plot;
    }

    public float getRangeValue() {
        return rangeValue;
    }

    public void setRangeValue(float rangeValue) {
        this.rangeValue = rangeValue;
    }

    public abstract String getLabel();
    public abstract void render(ArrayList<PowerRecord> records);
}


abstract class OnOffStatRenderer extends StatRenderer {
    public OnOffStatRenderer (PowerStatsPlot plot) {
        super(plot);
    }


    LineAndPointFormatter getFormatter() {
        LineAndPointFormatter formatter = new LineAndPointFormatter(
                getColorResourceId(), null, null, null);

        Paint linePaint = formatter.getLinePaint();
        linePaint.setStrokeWidth(plot.getContext().getResources().getDimension(R.dimen.plot_line_width));
        linePaint.setPathEffect(new CornerPathEffect(10));
        return formatter;
    }

    public void render(ArrayList<PowerRecord> records) {
        LineAndPointFormatter formatter = getFormatter();

        // DATA
        ArrayList<Number> values = new ArrayList<Number>();
        for (PowerRecord r: records) {
            values.add(r.getTimestamp());
            if (isOn(r)) {
                values.add(getRangeValue());
            } else {
                values.add(null);
            }
        }

        SimpleXYSeries series = new SimpleXYSeries(getLabel());
        series.setModel(values, SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED);
        plot.addSeries(series, formatter);
    }

    abstract boolean isOn(PowerRecord r);
    abstract int getColorResourceId();
}


final class WifiRenderer extends OnOffStatRenderer {
    public WifiRenderer(PowerStatsPlot plot) {
        super(plot);
    }

    public String getLabel() {
        return "WiFi";
    }

    @Override
    boolean isOn(PowerRecord r) {
        return r.getWifiState() == PowerRecord.WIFI_STATE_ENABLED;
    }

    @Override
    int getColorResourceId() {
        return R.color.plot_wifi_enabled;
    }
}

final class MobileDataRenderer extends OnOffStatRenderer {
    public MobileDataRenderer(PowerStatsPlot plot) {
        super(plot);
    }

    public String getLabel() {
        return "Mobile data";
    }

    @Override
    boolean isOn(PowerRecord r) {
        return r.getMobileDataState() == PowerRecord.MOBILE_DATA_ON;
    }

    @Override
    int getColorResourceId() {
        return R.color.plot_mobile_data_on;
    }
}

final class GpsRenderer extends OnOffStatRenderer {
    public GpsRenderer(PowerStatsPlot plot) {
        super(plot);
    }

    public String getLabel() {
        return "GPS";
    }

    @Override
    boolean isOn(PowerRecord r) {
        return r.getGpsState() == PowerRecord.GPS_STATE_ON;
    }

    @Override
    int getColorResourceId() {
        return R.color.plot_gps_on;
    }
}

final class PhoneServiceRenderer extends OnOffStatRenderer {
    public PhoneServiceRenderer(PowerStatsPlot plot) {
        super(plot);
    }

    public String getLabel() {
        return "Mobile";
    }

    @Override
    boolean isOn(PowerRecord r) {
        return r.getPhoneServiceState() == PowerRecord.PHONE_SERVICE_POWER_ON;
    }

    @Override
    int getColorResourceId() {
        return R.color.plot_phone_service_on;
    }
}

final class ScreenStateRenderer extends OnOffStatRenderer {
    public ScreenStateRenderer(PowerStatsPlot plot) {
        super(plot);
    }

    public String getLabel() {
        return "Screen On/Off";
    }

    @Override
    boolean isOn(PowerRecord r) {
        return r.getScreenState() == PowerRecord.SCREEN_ON;
    }

    @Override
    int getColorResourceId() {
        return R.color.plot_screen_on;
    }
}




Java Source Code List

com.znasibov.powerstats.Autostart.java
com.znasibov.powerstats.MainActivity.java
com.znasibov.powerstats.PowerRecord.java
com.znasibov.powerstats.PowerRecordsListenerMixin.java
com.znasibov.powerstats.PowerRecordsListener.java
com.znasibov.powerstats.PowerStatsApplication.java
com.znasibov.powerstats.PowerStatsChartFragment.java
com.znasibov.powerstats.PowerStatsDatabase.java
com.znasibov.powerstats.PowerStatsLoggerService.java
com.znasibov.powerstats.PowerStatsPlot.java
com.znasibov.powerstats.QuickStatsFragment.java
com.znasibov.powerstats.UserPreferences.java
com.znasibov.powerstats.Util.java