Back to project page SpunkyCharts.
The source code is released under:
GNU General Public License
If you think the Android project SpunkyCharts listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package com.jogden.spunkycharts.pricebyvolumechart; /* /*w w w . j ava 2 s . com*/ Copyright (C) 2014 Jonathon Ogden < jeog.dev@gmail.com > This program 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 3 of the License, or (at your option) any later version. This program 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 program. If not, see http://www.gnu.org/licenses. */ import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.NavigableSet; import java.util.UUID; import java.util.concurrent.BlockingDeque; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.os.ConditionVariable; import android.os.Handler; import android.support.v4.content.LocalBroadcastManager; import android.text.format.Time; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import com.jogden.spunkycharts.ApplicationPreferences; import com.jogden.spunkycharts.MainApplication; import com.jogden.spunkycharts.R; import com.jogden.spunkycharts.data.DataClientLocalDebug; import com.jogden.spunkycharts.data.DataContentService; import com.jogden.spunkycharts.data.DataContentService.DataClientInterface; import com.jogden.spunkycharts.data.DataContentService.DataConsumerInterface; import com.jogden.spunkycharts.misc.OHLC; import com.jogden.spunkycharts.misc.Pair; import com.jogden.spunkycharts.traditionalchart.YAxisPriceLabel; import com.jogden.spunkycharts.pricebyvolumechart.draw.DrawSemantics_SILO; @SuppressLint("InflateParams") public class PriceByVolumeChartFragmentAdapter implements DataContentService.DataConsumerInterface { @SuppressWarnings("serial") public static class PriceByVolumeChartLogicError extends RuntimeException { public PriceByVolumeChartLogicError(String msg){ super(msg); } } /**/ public final String MY_ID = UUID.randomUUID().toString(); /**/ private String _symbol = null; private final int MSPM = 60000; private final int GMS = MSPM * DataClientInterface.TIME_GRANULARITY; /* the important child views */ private final PriceByVolumeChartPanel _mainSgmnt; private final LinearLayout _priceAxis; private final LinearLayout _volumeAxis; private final ViewGroup _myContainer; private final LayoutInflater _inflater; private final Context _cntxt; private final Handler _guiThrdHndlr; /* also chart specific settings but * currently changed for all charts of this type */ private int _axisFontSz = ApplicationPreferences.getAxisFontSize(); private int _axisFontColor = ApplicationPreferences.getAxisFontColor(); private int _axisTimeout = ApplicationPreferences.getLongTimeout(); private int _axisTimeoutIncr = ApplicationPreferences.getTimeoutIncrement(); private int _priceElemHght = 0; private int _volSegHght = 0; private volatile float _highPrice = 0; private volatile float _lowPrice = Float.MAX_VALUE; private volatile int _highVolume = 0; private int _segCount = 0; private float _segSize = 0; private volatile Time _updateTime = new Time(); private Cursor _myCursor; static private String[] columns = { "OPEN_COLUMN", "HIGH_COLUMN", "LOW_COLUMN", "CLOSE_COLUMN", "VOLUMN_COLUMN" }; @SuppressWarnings("serial") static private List<Pair<String,SQLType>> myColumnInfo = new ArrayList<Pair<String,SQLType>>(){{ add(new Pair<String,SQLType>( columns[0],SQLType.FloatNotNull)); add(new Pair<String,SQLType>( columns[1],SQLType.FloatNotNull)); add(new Pair<String,SQLType>( columns[2],SQLType.FloatNotNull)); add(new Pair<String,SQLType>( columns[3],SQLType.FloatNotNull)); add(new Pair<String,SQLType>( columns[4],SQLType.IntNotNull)); }}; private AtomicBoolean _hasSegment = new AtomicBoolean(false); private AtomicBoolean _streamReady = new AtomicBoolean(false); private AtomicBoolean _updateReady = new AtomicBoolean(false); private volatile OHLC _lastGranularPriceSeg = new OHLC(0); private volatile int _lastGranularVolSeg = 0; private volatile Time _lastGranularSegTime = new Time(); private final Lock _volumeAxisLck = new ReentrantLock(); private final Lock _priceAxisLck = new ReentrantLock(); private volatile Thread _priceAxisThrd = null; private final Object _priceAxisThrdMntr = new Object(); private int _priceAxisPrmptCnt = 0; private final BlockingDeque<Thread> _priceAxisPrmptStck = new LinkedBlockingDeque<Thread>(); private final NavigableSet<Thread> _activeThreads = new ConcurrentSkipListSet<Thread>( new Comparator<Thread>(){ @Override public int compare(Thread lhs, Thread rhs) { return (int)lhs.getId() - (int)rhs.getId(); }}); private LocalBroadcastManager _localBcastMngr; private static final IntentFilter _rangeFltr = new IntentFilter(); static{ _rangeFltr.addAction(HighLowChange.NEW_HIGH_PRICE); _rangeFltr.addAction(HighLowChange.NEW_LOW_PRICE); _rangeFltr.addAction(HighLowChange.NEW_HIGH_VOL); } /*|---------> begin PUBLIC interface <--------|*/ public PriceByVolumeChartFragmentAdapter( Context context, Handler handler, LocalBroadcastManager broadcastManager, PriceByVolumeChartPanel mainChartPanel, LinearLayout volumeAxisLayout, LinearLayout priceAxisLayout, ViewGroup fragmentViewGroup, int volumeSegmentHeight, Cursor cursor, String symbol, Boolean... bArgs ){ this._cntxt = context; this._guiThrdHndlr = handler; this._localBcastMngr = broadcastManager; this._mainSgmnt = mainChartPanel; this._volumeAxis = volumeAxisLayout; this._priceAxis = priceAxisLayout; this._myContainer = fragmentViewGroup; this._volSegHght = volumeSegmentHeight; this._myCursor = cursor; this._symbol = symbol; this._localBcastMngr.registerReceiver( new HighLowChange(), _rangeFltr ); this._inflater = LayoutInflater.from(_cntxt); this._setPricePanelDrawSemantics(); _mainSgmnt.init( new PriceByVolumeChartPanel.OnNewHighVolume() { @Override public void onNewHighVolume(int v) { Intent iii = new Intent(HighLowChange.NEW_HIGH_VOL); iii.putExtra(HighLowChange.EXTRA_VALUE, v); iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID); _localBcastMngr.sendBroadcast( iii ); } }, handler ); if (_myCursor != null) this._init(); } @Override public boolean updateReady() { return _updateReady.get(); } @Override public Cursor swapCursor(Cursor cursor) { Cursor tmp = _myCursor; _myCursor = cursor; if (_myCursor != null) reset(); return tmp; } @Override public void update( DataClientInterface dataClient, DataConsumerInterface.InsertCallback iCallback ){ Pair<OHLC,Time> pricePair = dataClient.getLast( _symbol, DataClientLocalDebug.DATA_PRICE_DEF ); Pair<Integer,Time> volPair = dataClient.getLast( _symbol, DataClientLocalDebug.DATA_VOL_DEF ); OHLC price = new OHLC(pricePair.first); int vol = volPair.first.intValue(); Time time = new Time(pricePair.second); /* if we've rolled past TIME_GRANULARITY */ if( _hasSegment.get() && _segHasRolled( _lastGranularSegTime, time, DataClientInterface.TIME_GRANULARITY) ){ iCallback.insertRow( _createContentValues( _lastGranularPriceSeg, _lastGranularVolSeg, _lastGranularSegTime ), _symbol, (DataConsumerInterface)this ); _lastGranularSegTime = _truncateTime(time,GMS,true,false); /* Log.d("Data-Consumer-Update-DB", _lastGranularSegTime.format("%H:%M:%S") ); */ } _lastGranularPriceSeg = new OHLC(price); _lastGranularVolSeg = vol; if( !_streamReady.get() || _myCursor == null ){ _updateTime.set(time); return; } /* Log.d("Data-Consumer-Update-Current", _lastPriceSeg.toString() + " " + String.valueOf(_lastVolSeg) + " " +time.format("%H:%M:%S")); */ if (price.high > _highPrice) { _highPrice = price.high; Intent iii = new Intent(HighLowChange.NEW_HIGH_PRICE); iii.putExtra(HighLowChange.EXTRA_VALUE, price.high); iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID); _localBcastMngr.sendBroadcast( iii ); } if (price.low < _lowPrice) { _lowPrice = price.low; Intent iii = new Intent(HighLowChange.NEW_LOW_PRICE); iii.putExtra(HighLowChange.EXTRA_VALUE, price.low); iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID); _localBcastMngr.sendBroadcast( iii ); } try{ /* remember vol is cumm. vis-a-vis the granular seg */ _mainSgmnt.update( _getBucketIndex(price.close), vol ); }catch(RuntimeException e){ //DEBUG Log.d("UPDATE EXCEPTION", "::BEGIN:: " + e.getMessage()); e.printStackTrace(); Log.d("UPDATE EXCEPTION", "::END:: " + e.getMessage()); } /* high volume is checked in the panel, where it is agglomerated */ _updateTime.set(time); } @Override public void bridge( DataClientInterface dataClient, DataConsumerInterface.InsertCallback iCallback ){ _updateReady.set(false); iCallback.clear(_symbol,(DataConsumerInterface)this); Pair<OHLC[],Time> pricePair = dataClient.getBulk( _symbol, null, DataClientLocalDebug.DATA_PRICE_DEF ); Pair< Integer[],Time> volPair = dataClient.getBulk( _symbol, null, DataClientLocalDebug.DATA_VOL_DEF ); final OHLC[] prices = pricePair.first; final Integer[] vols = volPair.first; int len = vols.length; if(prices.length != len) throw new IllegalStateException( "price / volume getBulk size inconconsistency" ); _updateTime.set(pricePair.second); --len; long endMs = _updateTime.toMillis(true); endMs /= GMS; endMs *= GMS; List<ContentValues> valsList = new ArrayList<ContentValues>(); Time t = new Time(); for(int i = 0; i < len; ++i){ t.set(endMs - ((len-i)*GMS)); valsList.add( _createContentValues( prices[i],vols[i],t) ); /*Log.d("Data-Consumer-Get-Bulk", prices[i].toString() +" " + String.valueOf(vols[i]) + " " + t.format("%H:%M:%S")); */ } OHLC price = prices[len]; _lastGranularPriceSeg = new OHLC(price); _lastGranularVolSeg = vols[len]; _lastGranularSegTime.set(endMs); _updateReady.set(true); iCallback.insertRows( valsList,_symbol,(DataConsumerInterface)this ); } @Override public List<Pair<String, SQLType>> getColumns() { return myColumnInfo; } public void reset(){ cleanUpAndUpdate(); _init(); } public void cleanUpAndUpdate() { _streamReady.set( false); for( Thread t : _activeThreads ) t.interrupt(); _clear(); /* leave display values */ _highPrice = 0; _lowPrice = Float.MAX_VALUE; _highVolume = 0; } public void axisRefresh() { try{ _guiThrdHndlr.post( new Runnable(){ public void run(){ _volumeAxis.invalidate(); }}); }catch(IllegalStateException e){ e.printStackTrace(); } } /* adjust vertical zoom * NEED TO REDRAW EVERYTHING FOR THIS public void setSegmentHeight(int h) { if( h > MAX_SEG_HEIGHT ) h = MAX_SEG_HEIGHT; else if( h < MIN_SEG_HEIGHT ) h = MIN_SEG_HEIGHT; _volSegHght = h; _mainSgmnt.setSegmentHeight(h); _reset(); }*/ public int getSegmentHeight() { return _volSegHght; } public void setAxisFontSize(int sp) { _axisFontSz = sp; reset(); } public void setAxisFontColor(int colorId) { _axisFontColor = colorId; axisRefresh(); } public void setAxisTimeout(int value) { /* should reset but cost not worth benefit*/ _axisTimeout = value; axisRefresh(); } public void setTimeoutIncrement(int value) { /* should reset but cost not worth benefit*/ _axisTimeoutIncr = value; axisRefresh(); } /*|---------> end PUBLIC interface <--------|*/ /*|---------> begin PRIVATE methods <--------|*/ // CONSIDER MAKING THESE GLOBAL UTILITIES private boolean _segHasRolled(Time t1, Time t2, int minutes) { /* this only checks up to hours, i.e. same time tomorrow won't be treated as a roll (erroneously) */ int rMin1 = t1.minute / minutes; int rMin2 = t2.minute / minutes; return ( rMin1 != rMin2 || t1.hour != t2.hour); } private Time _truncateTime( Time t, long msInterval, boolean ignoreDST, boolean inPlace ){ long ms = t.toMillis(ignoreDST); ms /= msInterval; ms *= msInterval; if(inPlace){ t.set(ms); return t; } else { Time nTime = new Time(); nTime.set(ms); return nTime; } } private void _createVolumeAxisX(){ Thread thisThread = new Thread() { public void run() { if( !_volumeAxisLck.tryLock()) return; try{ final YAxisPriceLabel tv = (YAxisPriceLabel)_inflater.inflate( R.layout.y_axis_price_label, null ); tv.setText( Integer.toString(_highVolume)); tv.setTextSize(_axisFontSz); tv.setTextColor(_axisFontColor); _guiThrdHndlr.post( new Runnable(){ public void run(){ _volumeAxis.removeAllViewsInLayout(); _volumeAxis.addView(tv); }}); /*adjust volume axis to align*/ int tvWidth = 0; int failCount = 0; while( (tvWidth = tv.getWidth() / 2) == 0 ) { Thread.sleep(_axisTimeoutIncr); if( (failCount+=_axisTimeoutIncr) >= _axisTimeout ) return; /* if timeout forget about align */ } _mainSgmnt.setHorizontalBuffers( 0, tvWidth ); _guiThrdHndlr.post( new Runnable(){ public void run(){ _mainSgmnt.forceDraw(); }}); }catch(InterruptedException e) { }catch(RuntimeException e ){ throw e; }finally{ _activeThreads.remove( this ); _volumeAxisLck.unlock(); } } }; _activeThreads.add(thisThread); thisThread.start(); } private int _getBucketIndex(float p) { return (int) ((p - _lowPrice + (_segSize/2f)) / _segSize); } /* sync to avoid race with _agglomerate */ private synchronized void _findPriceRange() { float tmpHigh, tmpLow; _myCursor.moveToPosition(-1); while ( _myCursor.moveToNext() ) { tmpHigh = _myCursor.getFloat(2); tmpLow = _myCursor.getFloat(3); if(tmpHigh > _highPrice) _highPrice = tmpHigh; if(tmpLow < _lowPrice) _lowPrice = tmpLow; } _myCursor.moveToPosition(-1); } /// adjust high and low the assumed high and low from // end buckets/segs private synchronized void _agglomerate() { // should we just pass these in vs global?? if(_segCount <= 0 || _segSize <= 0){ throw new IllegalStateException( "_segCount and _segSize can't be <= 0" ); } final int[] buckets = new int[_segCount]; Arrays.fill(buckets, 0); float mPrice; int gVol; _myCursor.moveToPosition(-1); /* loop through and agglomerate the volumes by bucket */ while ( _myCursor.moveToNext() ) { mPrice = /* take the midpoint of the OHLC */ ( _myCursor.getFloat(2) + _myCursor.getFloat(3) ) / 2; gVol = _myCursor.getInt(5); buckets[ _getBucketIndex(mPrice) ] += gVol; } _myCursor.moveToPosition(-1); /* pass the buckets to the panel */ _mainSgmnt.populate(buckets); //, true); _streamReady.set(true); } private Thread _createPriceAxisAndMainPanel() { Thread thisThread = new Thread() { public void run() { /////////////////////////////////// /* START PREEMPTION LOGIC */ ///////////////////////////////// boolean noWait; final int MAX_PREEMPT_COUNT = ApplicationPreferences.getMaxPreemptCount(); if( !(noWait = _priceAxisLck.tryLock()) ) { synchronized(_priceAxisThrdMntr){ if( _priceAxisPrmptCnt++ > MAX_PREEMPT_COUNT ) return; if( _priceAxisThrd != null) _priceAxisThrd.interrupt(); } _priceAxisPrmptStck.offer(this); _priceAxisLck.lock(); } try{ /* everything that follows should assume we own monitor */ if( !noWait) { if( this != _priceAxisPrmptStck.peekLast()) return; else { /* don't assume stack hasn't grown since the peek */ Iterator<Thread> dIter = _priceAxisPrmptStck.iterator(); do{ _priceAxisPrmptStck.poll(); }while(dIter.next() != this); } } synchronized(_priceAxisThrdMntr){ _priceAxisThrd = this; } ///////////////////////////////// /* END PREEMPTION LOGIC */ ////////////////////////////// float rangeDiff = _highPrice - _lowPrice; /* deal with problematic high/low parameters */ if (rangeDiff <= 0 || _highPrice < 0 || _lowPrice < 0) if (rangeDiff == 0 && _highPrice > 0) { _highPrice *= 1.001; _lowPrice *= .999; rangeDiff = _highPrice - _lowPrice; } else throw new IllegalStateException( "Invalid high and/or low price in the ChartAdapter" ); /* if we haven't calculated the height of a price-label view */ if( _priceElemHght == 0) {/* can cached value doesn't go stale? */ final YAxisPriceLabel tv1 = (YAxisPriceLabel)_inflater.inflate( R.layout.y_axis_price_label, null ); tv1.setText("X"); tv1.setTextSize(_axisFontSz); tv1.setVisibility(View.INVISIBLE); final ConditionVariable cond = new ConditionVariable(); _guiThrdHndlr.post( new Runnable(){ public void run(){ _priceAxis.removeAllViews(); _priceAxis.addView(tv1); cond.open(); }}); cond.block(); cond.close(); YAxisPriceLabel tv1b = (YAxisPriceLabel)_priceAxis.getChildAt(0); /* make sure a valid priceElemHeightt, or quit entirely */ /* just spin, a new thread preempts us anyway */ while( (_priceElemHght = tv1b.getHeight()) == 0 ) Thread.sleep(_axisTimeoutIncr); } _guiThrdHndlr.post( new Runnable(){ public void run(){ _priceAxis.removeAllViews(); }}); int totalHeight; /* make sure a valid totalHeight, or quit entirely */ /* just spin, a new thread preempts us anyway */ while( (totalHeight = _priceAxis.getHeight()) == 0 || totalHeight > _myContainer.getHeight() ) Thread.sleep(_axisTimeoutIncr); float[] incrVals = new float[2]; try{ int maxNodes = (int)(totalHeight / _priceElemHght); if(rangeDiff < 0 || maxNodes < 0) throw new PriceByVolumeChartLogicError( "rangeDiff and maxNodes can't be negative." ); incrVals = /* call down to our native sub to find increment values */ MainApplication.IncrementRegressNative( rangeDiff, maxNodes ); if( incrVals[0] < 0 || incrVals[1] <= 0) throw new PriceByVolumeChartLogicError( "IncrementRegressNative() sub-routine aborted. " + "retVals[0]: " + incrVals[0] + "retVals[1]: " + incrVals[1] + "adjRangeDiff: " + rangeDiff + "maxNodes" + maxNodes ); _segCount = (int)incrVals[1]; _segSize = incrVals[0]; }catch(PriceByVolumeChartLogicError e){ Log.e("PriceByVolumeChartLogicError", e.getMessage()); return; /* just leave the axis empty*/ } /* adjust height to new increment values */ final int adjPriceElemHeight = (int)(totalHeight / _segCount); /* we'll need to account for any unused spaced in the axis */ final int vacantHeight = totalHeight - (int)(adjPriceElemHeight * _segCount); double distFromIncr = Math.IEEEremainder( (double)_highPrice, (double)_segSize ); /* distance from rounded incr value */ double adjTopNodeVal = (double)_highPrice - distFromIncr; if(distFromIncr > 0) /* be sure to round to the upper incr */ adjTopNodeVal += _segSize; DecimalFormat df = new DecimalFormat("0.00"); double lastNodeVal = adjTopNodeVal; int count = 0; do{ /* loop through the increments */ final YAxisPriceLabel tv = (YAxisPriceLabel)_inflater.inflate( R.layout.y_axis_price_label, null ); tv.setTextSize(_axisFontSz); tv.setText( df.format( lastNodeVal ) ); tv.setTextColor(_axisFontColor); tv.setLayoutParams( new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, 0, 1 ) ); _guiThrdHndlr.post( new Runnable() { public void run() { _priceAxis.addView(tv); }}); /* for the loop-contingent side-effect */ }while( ++count < (int)_segCount && (lastNodeVal-=_segSize) > 0); final float halfVacantHeight = vacantHeight / 2; _mainSgmnt.setVerticalBuffers( (int)halfVacantHeight, (int)halfVacantHeight ); _guiThrdHndlr.post( new Runnable() { public void run() { _priceAxis.setPadding( /* center price-views */ 0, (int)halfVacantHeight,0,(int)halfVacantHeight ); _priceAxis.setClipChildren(false); _priceAxis.setClipToPadding(false); _priceAxis.invalidate(); }}); synchronized(_priceAxisThrdMntr) { _priceAxisPrmptCnt = 0; } }catch(InterruptedException e){ }catch(IllegalStateException e){ Log.d( "IllegalState(High, Low): ", "IllegalStateException caught in _createPriceAxisAndMainPanelY: " + Float.toString(_highPrice) + " - " + Float.toString(_lowPrice) ); }catch(RuntimeException e ){ e.printStackTrace(); throw e; }finally{ synchronized(_priceAxisThrdMntr) { _priceAxisThrd = null; } _activeThreads.remove(this); _priceAxisLck.unlock(); } } }; _activeThreads.add(thisThread); thisThread.start(); return thisThread; } private void _setPricePanelDrawSemantics() { _mainSgmnt.setDrawSemantics( DrawSemantics_SILO.class ); } private ContentValues _createContentValues( OHLC price, int volume, Time t ){ ContentValues tmp = new ContentValues(); tmp.put( DataConsumerInterface.primaryKey, t.toMillis(true) ); tmp.put(columns[0], price.open); tmp.put(columns[1], price.high); tmp.put(columns[2], price.low); tmp.put(columns[3], price.close); tmp.put(columns[4], volume); return tmp; } private void _init() { if ( _myCursor != null ) { final int INCR = ApplicationPreferences.getTimeoutIncrement(); final int TOUT = ApplicationPreferences.getLongTimeout(); int timeOutCount = 0; /* stall for draw completion */ while( _mainSgmnt.getMeasuredHeight() == 0 && (timeOutCount+=INCR) < TOUT ) try { Thread.sleep(INCR); } catch (InterruptedException e) { } try{ _findPriceRange(); _createVolumeAxisX(); _createPriceAxisAndMainPanel().join(); } catch (InterruptedException e) { }finally{ _agglomerate(); _mainSgmnt.forceDraw(); } }else throw new IllegalStateException( "ChartFragmentAdapter can not be initialized " + "without a valid Cursor" ); } private void _clear() { _activeThreads.clear(); _mainSgmnt.clear(); _priceAxis.removeAllViewsInLayout(); _volumeAxis.removeAllViewsInLayout(); } /*|---------> end PRIVATE methods <--------|*/ /*|---------> begin NESTED OBJECT DEFS <--------|*/ /* APPLICATION WIDE BROADCASTS: broadcast when a price * or volume update is at a new extreme. Currently these broadcasts * are only relevant within the adapter. */ class HighLowChange extends BroadcastReceiver { public static final String NEW_HIGH_PRICE = "com.jogden.integratedtrader.NEW_HIGH_PRICE"; public static final String NEW_LOW_PRICE = "com.jogden.integratedtrader.NEW_LOW_PRICE"; public static final String NEW_HIGH_VOL = "com.jogden.integratedtrader.NEW_HIGH_VOL"; public static final String EXTRA_VALUE = "EXTRA_VALUE"; public static final String FRAGMENT_ID = "FRAGMENT_ID"; @Override public void onReceive(Context c, Intent i) { /* if its for another chart exit quietly */ if( !i.getStringExtra(FRAGMENT_ID).equals(MY_ID) ) return; String action = i.getAction(); float price; if (action.equals(HighLowChange.NEW_HIGH_PRICE)) { price = i.getFloatExtra(EXTRA_VALUE, _highPrice); if( price > _highPrice ) { _highPrice = price; try { _createPriceAxisAndMainPanel().join(); } catch (InterruptedException e) { } finally{ _agglomerate(); } } } else if(action.equals(HighLowChange.NEW_LOW_PRICE)) { price = i.getFloatExtra(EXTRA_VALUE, _lowPrice); if( price < _lowPrice ) { _lowPrice = price; try { _createPriceAxisAndMainPanel().join(); } catch (InterruptedException e) { } finally{ _agglomerate(); } } } else if(action.equals(HighLowChange.NEW_HIGH_VOL)) { int vol = i.getIntExtra(EXTRA_VALUE, _highVolume); if( vol > _highVolume ) { _highVolume = vol; _createVolumeAxisX(); } } } } }