/* * Copyright (C) 2012 Pixmob (http://github.com/pixmob) * * 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 org.pixmob.freemobile.netstat.ui; import org.pixmob.freemobile.netstat.Event; import org.pixmob.freemobile.netstat.MobileOperator; import org.pixmob.freemobile.netstat.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Shader; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; /** * Custom component showing battery levels with a chart. * @author Pixmob */ public class BatteryChart extends View { private Bitmap cache; private int freeMobileColor; private int orangeColor; private Paint mobileOperatorPaint; private int bgColor1; private int bgColor2; private Paint bgPaint; private Paint yBarPaint; private Paint yTextPaint; private Paint batteryLevelBorderPaint; private Paint batteryLevelPaint; private Paint cursorPaint; private Paint textCursorPaint; private Event[] events; private float touchX = -1; private float graphLeft; private int graphRight; private int graphBottom; private float graphTop; private float textCursorLeft; private float textCursorBottom; private String textCursor; private int textCursorFormat = DateUtils.FORMAT_SHOW_TIME; public BatteryChart(final Context context, final AttributeSet attrs) { super(context, attrs); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); invalidateCache(); } @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); if (MotionEvent.ACTION_DOWN == action || MotionEvent.ACTION_MOVE == action) { final float x = event.getX(); if (x >= graphLeft && x <= graphRight) { if (events != null && events.length != 0) { final long t0 = events[0].timestamp; final long t = Math.round((x - graphLeft) / getXFactor()) + t0; textCursor = DateUtils.formatDateTime(getContext(), t, textCursorFormat); touchX = x; invalidate(); return true; } } } if (MotionEvent.ACTION_UP == action) { touchX = -1; invalidate(); return true; } return super.onTouchEvent(event); } private float getXFactor() { if (events == null || events.length == 0) { return 0; } return (graphRight - graphLeft) / (events[events.length - 1].timestamp - events[0].timestamp); } private void doDraw(Canvas canvas) { final float y0Mob = 0; // Draw every lines with a single call to Canvas#drawLines, for better // performance. final float[] lines = new float[12 * 4]; int lineIdx = 0; lines[lineIdx++] = graphLeft; lines[lineIdx++] = graphTop; lines[lineIdx++] = graphLeft; lines[lineIdx++] = graphBottom; lines[lineIdx++] = graphLeft; lines[lineIdx++] = graphBottom; lines[lineIdx++] = graphRight; lines[lineIdx++] = graphBottom; // Draw Y units. final float x0Text = graphLeft - toDip(5); final float yFactor = (graphBottom - graphTop) / 100f; final int bandSize = 10; final float yBand = yFactor * bandSize; final float yAscent2 = yTextPaint.ascent() / 2; for (int i = bandSize; i <= 100; i += bandSize) { if (bgPaint.getColor() == bgColor2) { bgPaint.setColor(bgColor1); } else { bgPaint.setColor(bgColor2); } final float y = graphBottom - yFactor * i; canvas.drawRect(graphLeft, y, graphRight, y + yBand, bgPaint); if (i != 100) { canvas.drawText(String.valueOf(i), x0Text, y - yAscent2, yTextPaint); } lines[lineIdx++] = graphLeft; lines[lineIdx++] = y; lines[lineIdx++] = graphRight; lines[lineIdx++] = y; } final boolean drawChart = events != null && events.length > 1; Path batteryLevelBorderPath = null; if (drawChart) { final long t0 = events[0].timestamp; final float xFactor = getXFactor(); final int eventCount = events.length; final Path batteryPath = new Path(); batteryPath.moveTo(graphLeft, graphBottom); batteryPath.incReserve(eventCount + 2); float lastY = 0; for (int i = 0; i < eventCount; ++i) { final Event e = events[i]; float x = (e.timestamp - t0) * xFactor + graphLeft; if (x < graphLeft) { continue; } final float y = graphBottom - e.batteryLevel * yFactor; if (i != 0) { batteryPath.lineTo(x, y); } else { batteryPath.moveTo(x, y); } lastY = y; final MobileOperator mobOp = MobileOperator.fromString(e.mobileOperator); if (mobOp != null) { float x2 = graphLeft; if (i != 0) { final Event e0 = events[i - 1]; if (e.powerOn && !e0.powerOn) { continue; } final MobileOperator mobOp0 = MobileOperator.fromString(e0.mobileOperator); if (mobOp0 != null) { if (MobileOperator.FREE_MOBILE.equals(mobOp0)) { mobileOperatorPaint.setColor(freeMobileColor); } else if (MobileOperator.ORANGE.equals(mobOp0)) { mobileOperatorPaint.setColor(orangeColor); } x2 = (e0.timestamp - t0) * xFactor + graphLeft; if (x2 < graphLeft) { x2 = graphLeft; } if (x2 > graphRight) { x2 = graphRight; } canvas.drawLine(x2, y0Mob, x, y0Mob, mobileOperatorPaint); } } } } batteryLevelBorderPath = new Path(batteryPath); batteryPath.lineTo(graphRight, lastY); batteryPath.lineTo(graphRight, graphBottom); batteryPath.lineTo(graphLeft, graphBottom); canvas.drawPath(batteryPath, batteryLevelPaint); } // Draw axes. canvas.drawLines(lines, yBarPaint); if (drawChart) { canvas.clipRect(graphLeft, 0, graphRight, graphBottom); canvas.drawPath(batteryLevelBorderPath, batteryLevelBorderPaint); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Lazy initialize paint properties, once. if (batteryLevelPaint == null) { batteryLevelPaint = new Paint(); batteryLevelPaint.setStyle(Paint.Style.FILL); final int c1 = getResources().getColor(R.color.battery_level_color1); final int c2 = getResources().getColor(R.color.battery_level_color2); batteryLevelPaint.setShader(new LinearGradient(0, 0, 0, getHeight(), c1, c2, Shader.TileMode.CLAMP)); } if (batteryLevelBorderPaint == null) { batteryLevelBorderPaint = new Paint(); batteryLevelBorderPaint.setAntiAlias(true); batteryLevelBorderPaint.setStyle(Paint.Style.STROKE); batteryLevelBorderPaint.setStrokeWidth(4); batteryLevelBorderPaint.setStrokeCap(Paint.Cap.ROUND); batteryLevelBorderPaint.setStrokeJoin(Paint.Join.ROUND); batteryLevelBorderPaint.setColor(getResources().getColor(R.color.battery_level_border_color)); } if (bgPaint == null) { bgColor1 = getResources().getColor(R.color.battery_bg_color1); bgColor2 = getResources().getColor(R.color.battery_bg_color2); bgPaint = new Paint(); bgPaint.setStyle(Paint.Style.FILL); } if (yTextPaint == null) { yTextPaint = new Paint(); yTextPaint.setAntiAlias(true); yTextPaint.setSubpixelText(true); yTextPaint.setColor(getResources().getColor(R.color.battery_y_text_color)); yTextPaint.setTextSize(getResources().getDimension(R.dimen.battery_y_text_size)); yTextPaint.setTextAlign(Paint.Align.RIGHT); yTextPaint.setStrokeWidth(2); graphLeft = yTextPaint.measureText("100") + 2; } if (yBarPaint == null) { yBarPaint = new Paint(); yBarPaint.setColor(getResources().getColor(R.color.battery_y_bar_color)); yBarPaint.setStrokeWidth(2); } if (mobileOperatorPaint == null) { mobileOperatorPaint = new Paint(); mobileOperatorPaint.setStrokeWidth(16); mobileOperatorPaint.setStyle(Paint.Style.STROKE); orangeColor = getResources().getColor(R.color.orange_network_color1); freeMobileColor = getResources().getColor(R.color.free_mobile_network_color1); } if (cursorPaint == null) { cursorPaint = new Paint(); cursorPaint.setAntiAlias(true); cursorPaint.setColor(getResources().getColor(R.color.battery_cursor_color)); cursorPaint.setStrokeWidth(2); cursorPaint.setStyle(Paint.Style.FILL); } if (textCursorPaint == null) { textCursorPaint = new Paint(); textCursorPaint.setAntiAlias(true); textCursorPaint.setSubpixelText(true); textCursorPaint.setColor(getResources().getColor(R.color.battery_text_cursor_color)); textCursorPaint.setTextSize(getResources().getDimension(R.dimen.battery_y_text_size)); textCursorPaint.setTextAlign(Paint.Align.RIGHT); textCursorBottom = -textCursorPaint.ascent() + toDip(5); graphTop = textCursorBottom + toDip(4); } // No image is available in the cache: render a new one. if (cache == null) { cache = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); final Canvas c = new Canvas(cache); doDraw(c); } canvas.drawBitmap(cache, 0, 0, null); if (touchX != -1) { canvas.drawLine(touchX, graphTop, touchX, graphBottom, cursorPaint); canvas.drawText(textCursor, textCursorLeft, textCursorBottom, textCursorPaint); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); invalidateCache(); graphRight = getWidth(); graphBottom = getHeight(); textCursorLeft = graphRight - toDip(2); } private float toDip(float pixels) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, pixels, getResources() .getDisplayMetrics()); } public void setData(Event[] events) { this.events = events; invalidateCache(); if (events != null && events.length != 0) { final long duration = events[events.length - 1].timestamp - events[0].timestamp; if (duration >= 86400 * 1000) { // Over a day: show date+time. textCursorFormat = DateUtils.FORMAT_SHOW_DATE; } else { // One day: show time only. textCursorFormat = DateUtils.FORMAT_SHOW_TIME; } } } private void invalidateCache() { if (cache != null) { cache.recycle(); cache = null; } } }