Java tutorial
/* * Copyright (c) Lightstreamer Srl * * 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 com.lightstreamer.demo.android; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import com.androidplot.xy.XYGraphWidget; import com.androidplot.xy.XYPlot; import com.lightstreamer.demo.android.LightstreamerClient.MpnStatusListener; import com.lightstreamer.ls_client.ExtendedTableInfo; import com.lightstreamer.ls_client.HandyTableListener; import com.lightstreamer.ls_client.SubscrException; import com.lightstreamer.ls_client.SubscribedTableKey; import com.lightstreamer.ls_client.UpdateInfo; import com.lightstreamer.ls_client.mpn.MpnInfo; import android.app.Activity; import android.graphics.RectF; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.View.OnTouchListener; import android.widget.TextView; import android.widget.ToggleButton; public class DetailsFragment extends Fragment { private static final String TAG = "Details"; public final static String[] numericFields = { "last_price", "pct_change", "bid_quantity", "bid", "ask", "ask_quantity", "min", "max", "open_price" }; public final static String[] otherFields = { "stock_name", "time" }; public final static String[] subscriptionFields = { "stock_name", "last_price", "time", "pct_change", "bid_quantity", "bid", "ask", "ask_quantity", "min", "max", "open_price" }; public final static String[] mpnSubscriptionFields = { "stock_name", "last_price", "time" }; private final SubscriptionFragment subscriptionHandling = new SubscriptionFragment(); private Handler handler; HashMap<String, TextView> holder = new HashMap<String, TextView>(); Chart chart = new Chart(); ToggleButton toggle; public static final String ARG_ITEM = "item"; public static final String ARG_PN_CONTROLS = "pn_controls"; int currentItem = 0; private ItemSubscription currentSubscription = null; private View toggleContainer; private boolean pnEnabled; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler = new Handler(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { pnEnabled = false; // If activity recreated (such as from screen rotate), restore // the previous article selection set by onSaveInstanceState(). // This is primarily necessary when in the two-pane layout. if (savedInstanceState != null) { currentItem = savedInstanceState.getInt(ARG_ITEM); pnEnabled = savedInstanceState.getBoolean(ARG_PN_CONTROLS); } // Inflate the layout for this fragment View view = inflater.inflate(R.layout.details_view, container, false); toggle = (ToggleButton) view.findViewById(R.id.pn_switch); toggleContainer = view.findViewById(R.id.toggle_container); this.enablePN(pnEnabled); holder.put("stock_name", (TextView) view.findViewById(R.id.d_stock_name)); holder.put("last_price", (TextView) view.findViewById(R.id.d_last_price)); holder.put("time", (TextView) view.findViewById(R.id.d_time)); holder.put("pct_change", (TextView) view.findViewById(R.id.d_pct_change)); holder.put("bid_quantity", (TextView) view.findViewById(R.id.d_bid_quantity)); holder.put("bid", (TextView) view.findViewById(R.id.d_bid)); holder.put("ask", (TextView) view.findViewById(R.id.d_ask)); holder.put("ask_quantity", (TextView) view.findViewById(R.id.d_ask_quantity)); holder.put("min", (TextView) view.findViewById(R.id.d_min)); holder.put("max", (TextView) view.findViewById(R.id.d_max)); holder.put("open_price", (TextView) view.findViewById(R.id.d_open_price)); final XYPlot plot = (XYPlot) view.findViewById(R.id.mySimpleXYPlot); chart.setPlot(plot); plot.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (!pnEnabled) { //push notifications not enabled, ignore the touch return false; } v.performClick(); int action = event.getActionMasked(); float touchY = event.getY(); float touchX = event.getX(); XYGraphWidget widget = plot.getGraphWidget(); RectF gridRect = widget.getGridRect(); if (gridRect.contains(touchX, touchY)) { if (currentSubscription != null) { double triggerVal = widget.getYVal(touchY); chart.setMovingTriggerLine(triggerVal); if (action == MotionEvent.ACTION_UP) { triggerVal = Math.round(triggerVal * 100.0) / 100.0; chart.endMovingTriggerLine(triggerVal); Log.d(TAG, "Touch released @ " + triggerVal); //go on the network only after the touch has been released subscriptionHandling.activateMPN(getMpnInfo(triggerVal)); } } else { Log.v(TAG, "touch ignored"); } } return true; } }); return view; } @Override public void onStart() { super.onStart(); Bundle args = getArguments(); if (args != null) { updateStocksView(args.getInt(ARG_ITEM)); enablePN(args.getBoolean(ARG_PN_CONTROLS)); } else if (currentItem != 0) { updateStocksView(currentItem); } } @Override public void onPause() { super.onPause(); chart.onPause(); this.subscriptionHandling.onPause(); } @Override public void onResume() { super.onResume(); chart.onResume(this.getActivity()); this.subscriptionHandling.onResume(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); this.subscriptionHandling.onAttach(activity); } public void updateStocksView(int item) { if (item != currentItem || this.currentSubscription == null) { if (this.currentSubscription != null) { this.currentSubscription.disable(); } this.currentSubscription = new ItemSubscription("item" + item); this.subscriptionHandling.setSubscription(this.currentSubscription); currentItem = item; } } public int getCurrentStock() { return this.currentItem; } public void togglePN(ToggleButton toggle) { //TODO toggle status can be overridden by the onMpnStatusChanged: find a user friendly way to handle the case boolean on = toggle.isChecked(); if (currentItem != 0) { if (on) { Log.v(TAG, "PN enabled for item" + currentItem); this.subscriptionHandling.activateMPN(getMpnInfo(-1)); } else { Log.v(TAG, "PN disabled for item" + currentItem); this.subscriptionHandling.deactivateMPN(getMpnInfo(-1)); } } } public void enablePN(boolean enabled) { this.pnEnabled = enabled; this.showToggle(enabled); } private MpnInfo getMpnInfo(double trigger) { ExtendedTableInfo tableInfo = this.currentSubscription.getTableInfo(); Map<String, String> data = new HashMap<String, String>(); data.put("stock_name", "${stock_name}"); data.put("last_price", "${last_price}"); data.put("time", "${time}"); data.put("item", tableInfo.getItems()[0]); ExtendedTableInfo clone = null; try { clone = new ExtendedTableInfo(tableInfo.getItems(), "MERGE", mpnSubscriptionFields, false); } catch (SubscrException e) { Log.wtf(TAG, "can't happen"); } clone.setDataAdapter("QUOTE_ADAPTER"); MpnInfo info = new MpnInfo(clone, "Stock update", data); info.setDelayWhileIdle("false"); info.setTimeToLive("300"); //this.getLastPrice() info.setTriggerExpression(triggetToString(trigger, this.currentSubscription.getLastPrice())); return info; } public static final String TRIGGER_HEAD = "Double.parseDouble($[2])"; public static final String TRIGGER_LT = "<="; public static final String TRIGGER_GT = ">="; public String triggetToString(double triggerVal, double current) { String trigger = null; if (triggerVal >= 0) { trigger = TRIGGER_HEAD; if (triggerVal < current) { trigger += TRIGGER_LT; } else { trigger += TRIGGER_GT; } trigger += triggerVal; } return trigger; } private void showToggle(final boolean show) { if (toggleContainer == null) { //onCreateView will set it (or the layout does not contain it) return; } handler.post(new Runnable() { public void run() { if (toggleContainer == null) { return; } toggleContainer.setVisibility(show ? View.VISIBLE : View.GONE); } }); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(ARG_ITEM, currentItem); outState.putBoolean(ARG_PN_CONTROLS, this.pnEnabled); } private class ItemSubscription implements Subscription { private final Stock stock; private ExtendedTableInfo tableInfo; private SubscribedTableKey key; private StockListener listener; public ItemSubscription(String item) { this.stock = new Stock(item, numericFields, otherFields); stock.setHolder(holder); stock.setChart(chart); this.listener = new StockListener(stock); try { this.tableInfo = new ExtendedTableInfo(new String[] { item }, "MERGE", subscriptionFields, true); this.tableInfo.setDataAdapter("QUOTE_ADAPTER"); } catch (SubscrException e) { Log.wtf(TAG, "I'm pretty sure MERGE is compatible with the snapshot request!"); } } public void disable() { this.listener.disable(); } @Override public HandyTableListener getTableListener() { return this.listener; } @Override public SubscribedTableKey getTableKey() { return this.key; } @Override public ExtendedTableInfo getTableInfo() { return this.tableInfo; } @Override public void setTableKey(SubscribedTableKey key) { this.key = key; } @Override public MpnStatusListener getMpnStatusListener() { return this.listener; } public double getLastPrice() { return this.stock.getLastPrice(); } } private class StockListener implements HandyTableListener, MpnStatusListener { private AtomicBoolean disabled = new AtomicBoolean(false); private final Stock stock; public StockListener(Stock stock) { this.stock = stock; } public void disable() { disabled.set(true); } @Override public void onRawUpdatesLost(int arg0, String arg1, int arg2) { Log.wtf(TAG, "Not expecting lost updates"); } @Override public void onSnapshotEnd(int itemPos, String itemName) { Log.v(TAG, "Snapshot end for " + itemName); } @Override public void onUnsubscr(int itemPos, String itemName) { Log.v(TAG, "Unsubscribed " + itemName); } @Override public void onUnsubscrAll() { Log.v(TAG, "Unsubscribed all"); } @Override public void onUpdate(int itemPos, String itemName, UpdateInfo newData) { if (disabled.get()) { return; } Log.v(TAG, "Update for " + itemName); this.stock.update(newData, handler); } @Override public void onMpnStatusChanged(final boolean activated, String trigger) { if (disabled.get()) { return; } double triggerDouble = -1; if (trigger != null && !trigger.equals("")) { try { triggerDouble = Double .parseDouble(trigger.substring(TRIGGER_HEAD.length() + TRIGGER_GT.length())); } catch (NumberFormatException e) { Log.wtf(TAG, "Unexpected trigger set: " + trigger); } if (activated) { chart.setTriggerLine(triggerDouble); } else { chart.removeTriggerLine(triggerDouble); } } else { handler.post(new Runnable() { public void run() { if (toggle != null) { //toggle can be null if we're in landscape mode toggle.setChecked(activated); } } }); } } } }