package de.tu.darmstadt.seemoo.ansian.gui.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import de.greenrobot.event.Subscribe;
import de.tu.darmstadt.seemoo.ansian.control.SourceControl;
import de.tu.darmstadt.seemoo.ansian.control.StateHandler;
import de.tu.darmstadt.seemoo.ansian.control.events.DataEvent;
import de.tu.darmstadt.seemoo.ansian.model.WaveformDrawDataAdapter;
import de.tu.darmstadt.seemoo.ansian.model.preferences.ColorPreference;
import de.tu.darmstadt.seemoo.ansian.model.preferences.Preferences;
import de.tu.darmstadt.seemoo.ansian.model.sources.IQSourceInterface;
/**
* @author Steffen Kreis
*
* Displays data in time spectrum as waveform. Also takes care of
* scaling (zooming) internally as this is not needed outside this class
* anyway. Scrolling in paused mode is planned, not implemented by now.
* Imaginary part of data is not shown as it would decrease the size for
* the real part and therefore the user experience. The real part
* suffices to show desired information.
*
*
* Roughly oriented at
* https://github.com/google/ringdroid/blob/master/src/com/ringdroid/
* WaveformView.java
*/
public class WaveformView extends MySurfaceView {
private WaveformDrawDataAdapter drawDataAdapter;
private Paint mPaintRe;
private Paint mPaintIm;
private float shownDataAmount = 1;
private WaveformDrawDataAdapter wfDrawDataAdapter;
private final int MAXSAMPLEPACKETS = 20;
private IQSourceInterface source;
private final float MAX_YAMP = 70;
private final float MIN_YAMP = 1;
private long oldestShownSample;
private long newestShownSample = 0;
private String LOGTAG = "WaveformView";
private long freq;
private float yAmplifier = 15;
public WaveformView(Context context) {
this(context, null, 0);
}
public WaveformView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveformView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initPaint();
source = SourceControl.getSource();
oldestShownSample = source.getPacketSize();
newestShownSample = 0;
}
/**
* Returns the height/width of the frequency/power grid in px
*
* @return size of the grid (frequency grid height / power grid width) in px
*/
public int getGridSize() {
return (int) (75 * getResources().getDisplayMetrics().xdpi / 200);
}
private void initPaint() {
mPaintRe = new Paint();
mPaintRe.setStyle(Paint.Style.STROKE);
mPaintRe.setColor(Color.WHITE);
mPaintRe.setStrokeWidth(0);
mPaintRe.setAntiAlias(true);
mPaintIm = new Paint();
mPaintIm.setStyle(Paint.Style.STROKE);
mPaintIm.setColor(Color.GREEN);
mPaintIm.setStrokeWidth(0);
mPaintIm.setAntiAlias(true);
}
/**
* Updates the waveform view with a new "frame" of samples and renders it.
* The new frame gets added to the front of the rendering queue, pushing the
* previous frames back, causing them to be faded out visually.
*
* @param packet
* the most recent buffer of audio samples
*/
public synchronized void draw() {
// Draw:
Canvas canvas = null;
try {
canvas = this.getHolder().lockCanvas();
synchronized (this.getHolder()) {
if (canvas != null) {
// Draw all the components
drawWaveform(canvas);
} else
Log.d(LOGTAG, "draw: Canvas is null.");
}
} catch (Exception e) {
Log.e(LOGTAG, "draw: Error while drawing on the canvas. Stop!");
e.printStackTrace();
} finally {
if (canvas != null) {
this.getHolder().unlockCanvasAndPost(canvas);
}
}
}
/**
* Repaints the view's surface.
*
* @param canvas
* the {@link Canvas} object on which to draw
*/
private void drawWaveform(Canvas canvas) {
// Clear the screen each time because SurfaceView won't do this for us.
canvas.drawColor(Color.BLACK);
if (drawDataAdapter == null || !StateHandler.isPaused()) {
// drawDataAdapter = new
// WaveformDrawDataAdapter(DataHandler.getInstance().getWaveformDrawData(shownDataAmount));
freq = Preferences.GUI_PREFERENCE.getFrequency();
}
int width = getWidth();
int height = getHeight();
int centerY = height / 2;
float leftBorder = width * 0.01f;
float yPos = height * 0.01f;
Rect bounds = new Rect();
String text = String.format("Frequency: %4.3f MHz", freq / 1000000f)
+ String.format(" Time window: %5.3f ms", (shownDataAmount * SourceControl.getSource().getSampleRate())
/ SourceControl.getSource().getPacketSize());
ColorPreference.TEXT_SMALL_PAINT.getTextBounds(text, 0, text.length(), bounds);
yPos += bounds.height();
canvas.drawText(text, leftBorder, yPos, ColorPreference.TEXT_SMALL_PAINT);
if (StateHandler.isDemodulating()) {
text = String.format("showing demodulated Data (%4.3f MHz)",
Preferences.GUI_PREFERENCE.getDemodFrequency() / 1000000f);
ColorPreference.TEXT_SMALL_PAINT.getTextBounds(text, 0, text.length(), bounds);
yPos += bounds.height();
canvas.drawText(text, leftBorder, yPos, ColorPreference.TEXT_SMALL_PAINT);
}
wfDrawDataAdapter = new WaveformDrawDataAdapter();
float[] drawArrayRe = wfDrawDataAdapter.getDrawArrayRe(width, shownDataAmount, yAmplifier);
int arReLength = drawArrayRe.length;
if (drawArrayRe != null && arReLength > 0) {
for (int x = 0; x < arReLength - 1; x++) {
canvas.drawLine(x, centerY + yAmplifier * drawArrayRe[x], x + 1,
centerY + yAmplifier * drawArrayRe[x + 1], mPaintRe);
}
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean retVal = this.scaleGestureDetector.onTouchEvent(event);
retVal = this.gestureDetector.onTouchEvent(event) || retVal;
return retVal;
}
@Override
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return true;
}
@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
shownDataAmount = 1;
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// TODO calc and move
if (newestShownSample != 0
&& (oldestShownSample - newestShownSample) < (MAXSAMPLEPACKETS * source.getPacketSize())) {
oldestShownSample += distanceX;
newestShownSample += distanceX;
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float xScale = ((float) detector.getCurrentSpanX()) / detector.getPreviousSpanX();
yAmplifier *= (((float) detector.getCurrentSpanY()) / detector.getPreviousSpanY());
yAmplifier = Math.max(Math.min(yAmplifier, MAX_YAMP), MIN_YAMP);
shownDataAmount /= xScale;
shownDataAmount = Math.min(Math.max(1, (shownDataAmount)), MAXSAMPLEPACKETS);
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
}
@Override
public int getDrawDivisor() {
return 1;
}
@Subscribe
public void onEvent(DataEvent event) {
//
}
}