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.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.util.Log; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import de.greenrobot.event.EventBus; import de.greenrobot.event.Subscribe; import de.tu.darmstadt.seemoo.ansian.control.StateHandler; import de.tu.darmstadt.seemoo.ansian.control.events.ChangeChannelWidthEvent; import de.tu.darmstadt.seemoo.ansian.control.events.DemodScaleEvent; import de.tu.darmstadt.seemoo.ansian.control.events.DemodValueChangeEvent; import de.tu.darmstadt.seemoo.ansian.control.events.DemodulationEvent; import de.tu.darmstadt.seemoo.ansian.control.events.FrequencyEvent; import de.tu.darmstadt.seemoo.ansian.control.events.ScrollEvent; import de.tu.darmstadt.seemoo.ansian.control.events.SpectrumScaleEvent; import de.tu.darmstadt.seemoo.ansian.control.events.StateEvent; import de.tu.darmstadt.seemoo.ansian.control.events.TapEvent; import de.tu.darmstadt.seemoo.ansian.control.threads.Demodulator; import de.tu.darmstadt.seemoo.ansian.drawables.DemodTunerDrawable; import de.tu.darmstadt.seemoo.ansian.drawables.FftSpectrumDrawable; import de.tu.darmstadt.seemoo.ansian.drawables.FrequencyGridDrawable; import de.tu.darmstadt.seemoo.ansian.drawables.PerformanceInfoDrawable; import de.tu.darmstadt.seemoo.ansian.drawables.PowerGridDrawable; import de.tu.darmstadt.seemoo.ansian.drawables.WaterfallDrawable; import de.tu.darmstadt.seemoo.ansian.model.preferences.Preferences; /** * <h1>AnSiAn - Analyzer Surface</h1> * * Module: AnalyzerSurface.java Description: This is a custom view extending the * SurfaceView. It will show the frequency spectrum and the waterfall diagram. * * @author Dennis Mantz * @author Markus Grau * @author Steffen Kreis * * Copyright (C) 2014 Dennis Mantz License: * http://www.gnu.org/licenses/gpl.html GPL version 2 or higher * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ public class AnalyzerSurface extends MySurfaceView { public AnalyzerSurface(Context context) { super(context); } // organizes all drawables private LayerDrawable layDraw; // Drawables for info and data private DemodTunerDrawable demodTunerDrwb; private PowerGridDrawable powerGridDrwb; private FrequencyGridDrawable freqGridDrwb; private PerformanceInfoDrawable perfInfoDrwb; private FftSpectrumDrawable freqSpectrumDrwb; private WaterfallDrawable waterfallDrwb; // private MorseDrawable morseDrwb; private static final String LOGTAG = "AnalyzerSurface"; private boolean displayRelativeFrequencies = false; // indicates whether // frequencies on the // horizontal axis // should be // relative to the // center frequency // (true) or absolute // (false) private int demodWidth = -1; // (half) width of the channel filter of the // demodulator // based on the area in which a touch/scroll/scale event first occured, // different operations are needed private int touchtype = 0; private static final int TOUCHTYPE_NORMAL = 1; private static final int TOUCHTYPE_DEMOD = 2; private boolean init = false; /** * @param channelWidth * new channel width (cut-off frequency - single sided) of the * channel filter in Hz */ public void setChannelWidth(int channelWidth) { this.demodWidth = channelWidth; } /** * @return true if frequencies on the horizontal axis are displayed relative * to center freq; false if absolute */ public boolean isDisplayRelativeFrequencies() { return displayRelativeFrequencies; } /** * @param displayRelativeFrequencies * true if frequencies on the horizontal axis should be displayed * relative to center freq; false if absolute */ public void setDisplayRelativeFrequencies(boolean displayRelativeFrequencies) { this.displayRelativeFrequencies = displayRelativeFrequencies; } /** * * update GUI components (e.g. when orientation changed) */ public void updateGUI() { powerGridDrwb.setDimensions(getFftHeight()); demodTunerDrwb.setDimensions(getWidth(), getFftHeight()); waterfallDrwb.setDimensions(getFftHeight(), getWidth(), getHeight()); perfInfoDrwb.setDimensions(getHeight(), getWidth()); freqSpectrumDrwb.init(getFftHeight(), getWidth()); freqGridDrwb.init(getWidth(), getFftHeight(), isDisplayRelativeFrequencies()); } @Override public boolean onScale(ScaleGestureDetector detector) { Log.d(LOGTAG, "analyzersurfaceScale called"); if ((detector.getFocusX() > Preferences.GUI_PREFERENCE.getGridSize() * 1.5)) { float xScale = detector.getCurrentSpanX() / detector.getPreviousSpanX(); float yScale = detector.getCurrentSpanY() / detector.getPreviousSpanY(); switch (touchtype) { case TOUCHTYPE_DEMOD: EventBus.getDefault().post(new DemodScaleEvent(xScale, yScale)); break; case TOUCHTYPE_NORMAL: EventBus.getDefault().post(new SpectrumScaleEvent(xScale, yScale)); break; } } return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } @Override public boolean onDown(MotionEvent e) { // check if scrolling concerns demodulation if (StateHandler.isDemodulating()) { // check if touch was in spectrum area if (e.getY() < getFftHeight()) { // check if touch was in demod area float xPos = e.getX(); if (xPos > demodTunerDrwb.getLowerBWEnd() && xPos < demodTunerDrwb.getUpperBWEnd()) { touchtype = TOUCHTYPE_DEMOD; return true; } } } touchtype = TOUCHTYPE_NORMAL; return true; } @Override public void onShowPress(MotionEvent e) { // not used } @Override public boolean onSingleTapUp(MotionEvent e) { // Set the channel frequency to the tapped position EventBus.getDefault().post(new TapEvent(e.getX() / getWidth(), e.getY() / getFftHeight())); return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { switch (touchtype) { case TOUCHTYPE_NORMAL: EventBus.getDefault().post(new ScrollEvent(distanceX / getWidth(), distanceY / getHeight(), false)); break; case TOUCHTYPE_DEMOD: EventBus.getDefault().post(new ScrollEvent(distanceX / getWidth(), distanceY / getHeight(), true)); } return true; } @Override public void onLongPress(MotionEvent e) { // not used } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return true; } // ------------------- </OnGestureListener> // ----------------------------------// @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { if (init) { boolean retVal = this.scaleGestureDetector.onTouchEvent(event); retVal = this.gestureDetector.onTouchEvent(event) || retVal; return retVal; } return false; } /** * Returns the height of the fft plot in px (y coordinate of the bottom line * of the fft spectrum) * * @return height (in px) of the fft */ public int getFftHeight() { return (int) (getHeight() * Preferences.GUI_PREFERENCE.getFFTRatio()); } /** * Returns the height of the waterfall plot in px * * @return heigth (in px) of the waterfall */ public int getWaterfallHeight() { return (int) (getHeight() * (1 - Preferences.GUI_PREFERENCE.getFFTRatio())); } /** * Will (re-)draw the given data set on the surface. Note that it actually * only draws a sub set of the fft data depending on the current settings of * virtual frequency and sample rate. * * @param mag * array of magnitude values that represent the fft * @param frequency * center frequency * @param sampleRate * sample rate * @param frameRate * current frame rate (FPS) */ public void draw() { if (layDraw == null) init(); // Draw: Canvas dataCanvas = null; try { dataCanvas = this.getHolder().lockCanvas(); synchronized (this.getHolder()) { if (dataCanvas != null) { dataCanvas.drawColor(Color.BLACK); updateGUI(); layDraw.draw(dataCanvas); } 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 (dataCanvas != null) { this.getHolder().unlockCanvasAndPost(dataCanvas); } } } @Subscribe public void onEvent(DemodulationEvent event) { // DemoType demodulation = event.getDemodulation(); // MorseDrawable highly experimental, not built in // if (DemoType.MORSE == event.getDemodulation()) { // if (morseDrwb == null) { // morseDrwb = new MorseDrawable(getWidth() / 2, getFftHeight(), // getWidth() / 2, 100); // } // morseDrwb.setState(true); // } else { // // morseDrwb.setState(false); // } EventBus.getDefault().post(new ChangeChannelWidthEvent(demodWidth)); } private void init() { powerGridDrwb = new PowerGridDrawable(getFftHeight()); freqGridDrwb = new FrequencyGridDrawable(); perfInfoDrwb = new PerformanceInfoDrawable(getHeight(), getWidth()); freqSpectrumDrwb = new FftSpectrumDrawable(); waterfallDrwb = new WaterfallDrawable(getFftHeight(), getWidth(), getWaterfallHeight()); demodTunerDrwb = new DemodTunerDrawable(getWidth(), getFftHeight()); // if (morseDrwb == null) { // morseDrwb = new MorseDrawable(getWidth() / 2, getFftHeight() / 2, // getWidth() / 2, 100); // } layDraw = new LayerDrawable(new Drawable[] { waterfallDrwb, freqSpectrumDrwb, powerGridDrwb, freqGridDrwb, perfInfoDrwb, demodTunerDrwb });// , morseDrwb }); init = true; } @Subscribe public void onEvent(StateEvent event) { if (init) { updateGUI(); switch (event.getState()) { case SCANNING: if (Preferences.GUI_PREFERENCE.isScannerWaterfall()) layDraw = new LayerDrawable(new Drawable[] { waterfallDrwb, freqSpectrumDrwb, powerGridDrwb, freqGridDrwb, perfInfoDrwb }); else layDraw = new LayerDrawable( new Drawable[] { freqSpectrumDrwb, powerGridDrwb, freqGridDrwb, perfInfoDrwb }); break; case MONITORING: layDraw = new LayerDrawable(new Drawable[] { waterfallDrwb, freqSpectrumDrwb, powerGridDrwb, freqGridDrwb, perfInfoDrwb, demodTunerDrwb }); default: break; } } } @Subscribe public void onEvent(FrequencyEvent event) { freqSpectrumDrwb.resetPeaks(); } @Subscribe public void onEvent(DemodValueChangeEvent event) { updateGUI(); } }