package eu.hgross.blaubot.android.views; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import org.apache.commons.collections4.queue.CircularFifoQueue; import org.w3c.dom.Text; import java.util.ArrayList; import eu.hgross.blaubot.android.R; import eu.hgross.blaubot.core.Blaubot; import eu.hgross.blaubot.core.statemachine.IBlaubotConnectionStateMachineListener; import eu.hgross.blaubot.core.statemachine.states.FreeState; import eu.hgross.blaubot.core.statemachine.states.IBlaubotState; import eu.hgross.blaubot.core.statemachine.states.IBlaubotSubordinatedState; import eu.hgross.blaubot.core.statemachine.states.KingState; import eu.hgross.blaubot.core.statemachine.states.PrinceState; import eu.hgross.blaubot.core.statemachine.states.StoppedState; import eu.hgross.blaubot.ui.IBlaubotDebugView; /** * Android view to display informations about the StateMachine's history of states. * Add this view to a blaubot instance like this: stateView.registerBlaubotInstance(blaubot); * * @author Henning Gross {@literal (mail.to@henning-gross.de)} */ public class StateHistoryView extends LinearLayout implements IBlaubotDebugView { private static final String LOG_TAG = "StateHistoryView"; private Handler mUiHandler; private Blaubot mBlaubot; private final Object queueMonitor = new Object(); private CircularFifoQueue<StateEntry> stateHistoryQueue; private int mMaxStates = 20; /** * Contains the whole view and all sub elements. */ private LinearLayout mMainView; /** * Holds just the symbols of the states in order */ private LinearLayout mStateContainer; /** * Contains a more detailed view then the state container. * Is not visible by default - is made visible by tapping on the stateContainer */ private LinearLayout mDetailedStateContainer; public StateHistoryView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public StateHistoryView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { stateHistoryQueue = new CircularFifoQueue<>(mMaxStates); // will be changed on resize mUiHandler = new Handler(Looper.getMainLooper()); mMainView = (LinearLayout) inflate(getContext(), R.layout.blaubot_state_history_view, this); mStateContainer = (LinearLayout) mMainView.findViewById(R.id.stateHistoryIconsContainer); mDetailedStateContainer = (LinearLayout) mMainView.findViewById(R.id.detailsContainer); // addView(mMainView); } /** * Toggles the detailed view's visibility */ private final OnClickListener mToggleDetailViewOnClickListener = new OnClickListener() { @Override public void onClick(View v) { // toggle simple view final boolean stateViewVisible = mStateContainer.getVisibility() == VISIBLE; mStateContainer.setVisibility(stateViewVisible ? GONE : VISIBLE); // toggle detailed view final boolean detailedViewVisible = mDetailedStateContainer.getVisibility() == VISIBLE; mDetailedStateContainer.setVisibility(detailedViewVisible ? GONE : VISIBLE); } }; private IBlaubotConnectionStateMachineListener mBlaubotConnectionListener = new IBlaubotConnectionStateMachineListener() { @Override public void onStateChanged(IBlaubotState oldState, final IBlaubotState state) { synchronized (queueMonitor) { StateEntry entry = new StateEntry(state, System.currentTimeMillis()); stateHistoryQueue.add(entry); } updateUI(); } @Override public void onStateMachineStopped() { updateUI(); } @Override public void onStateMachineStarted() { updateUI(); } }; @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // calc the max number of state icons inside the view Drawable d = getResources().getDrawable(R.drawable.ic_free); final int drawable_w = d.getMinimumWidth(); final int drawable_h = d.getMinimumHeight(); d = null; final int newMaxStates; if (mStateContainer.getOrientation() == HORIZONTAL) { newMaxStates = w / drawable_w; } else { newMaxStates = h / drawable_h; } if (newMaxStates != mMaxStates) { mMaxStates = newMaxStates; // re-create the queue synchronized (queueMonitor) { CircularFifoQueue<StateEntry> newQueue = new CircularFifoQueue<>(newMaxStates); newQueue.addAll(stateHistoryQueue); stateHistoryQueue = newQueue; } // update the ui updateUI(); } super.onSizeChanged(w, h, oldw, oldh); } /** * Updates the whole ui (removing all, creating it all again) */ private void updateUI() { // get the states in a short synched block final ArrayList<StateEntry> states = new ArrayList<>(); synchronized (queueMonitor) { states.addAll(stateHistoryQueue); } // pre-ceate the simpleViews final ArrayList<ImageView> simpleViews = new ArrayList<>(states.size()); final ArrayList<View> detailedViews = new ArrayList<>(states.size()); StateEntry prevEntry = null; for (StateEntry entry : states) { // create the simple view Drawable d = ViewUtils.getDrawableForBlaubotState(getContext(), entry.getState()); ImageView iv = new ImageView(getContext()); iv.setImageDrawable(d); iv.setOnClickListener(mToggleDetailViewOnClickListener); // the more detailed view final View detailedView = createHistoryItem(getContext(), entry, prevEntry); detailedView.setOnClickListener(mToggleDetailViewOnClickListener); // add them to the list simpleViews.add(iv); detailedViews.add(detailedView); prevEntry = entry; } // add them to their containers on the ui thread mUiHandler.post(new Runnable() { @Override public void run() { mStateContainer.removeAllViews(); mDetailedStateContainer.removeAllViews(); // the main view for (ImageView iv : simpleViews) { mStateContainer.addView(iv); } // the detailed view for (View view : detailedViews) { mDetailedStateContainer.addView(view); } } }); } /** * Creates a view visualizing a state with some more details (when, order, ...) * * @param ctx the context * @param stateEntry the state entry * @param previousEntry the previous entry or null, if it is the first entry * @return */ private static View createHistoryItem(Context ctx, StateEntry stateEntry, StateEntry previousEntry) { final boolean isFirst = previousEntry == null; final View item = inflate(ctx, R.layout.blaubot_state_history_view_item, null); final ImageView imageView = (ImageView) item.findViewById(R.id.stateIcon); final View preContainer = item.findViewById(R.id.preContainer); final TextView timeDifferenceTextView = (TextView) item.findViewById(R.id.timeDifferenceTextView); final TextView text = (TextView) item.findViewById(R.id.text); final TextView text2 = (TextView) item.findViewById(R.id.text2); preContainer.setVisibility(isFirst ? GONE : VISIBLE); if (!isFirst) { long diff = stateEntry.getTime() - previousEntry.getTime(); timeDifferenceTextView.setText(diff + " ms"); } final IBlaubotState state = stateEntry.getState(); Drawable d = ViewUtils.getDrawableForBlaubotState(ctx, state); imageView.setImageDrawable(d); text.setText(state.getClass().getSimpleName()); if (state instanceof IBlaubotSubordinatedState) { text2.setText("King: " + ((IBlaubotSubordinatedState) state).getKingUniqueId()); } else if (state instanceof FreeState) { text2.setText("Alone, searching a kingdom ..."); } else if (state instanceof StoppedState) { text2.setText("Relaxing ..."); } else if (state instanceof PrinceState) { text2.setText("Waiting for the king to die ..."); } else if (state instanceof KingState) { text2.setText("Pulling all the strings ..."); } return item; } /** * Holds a state and some meta data to be visualized */ private class StateEntry { private final IBlaubotState state; private final long time; /** * @param state the state * @param time the time at which the state was first used */ public StateEntry(IBlaubotState state, long time) { this.state = state; this.time = time; } public IBlaubotState getState() { return state; } public long getTime() { return time; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; StateEntry that = (StateEntry) o; if (time != that.time) return false; return state.equals(that.state); } @Override public int hashCode() { int result = state.hashCode(); result = 31 * result + (int) (time ^ (time >>> 32)); return result; } } /** * Register this view with the given blaubot instance * * @param blaubot the blaubot instance to connect with */ public void registerBlaubotInstance(Blaubot blaubot) { if (mBlaubot != null) { unregisterBlaubotInstance(); } this.mBlaubot = blaubot; this.mBlaubot.getConnectionStateMachine().addConnectionStateMachineListener(mBlaubotConnectionListener); // force some updates final IBlaubotState currentState = blaubot.getConnectionStateMachine().getCurrentState(); mBlaubotConnectionListener.onStateChanged(null, currentState); } @Override public void unregisterBlaubotInstance() { if (mBlaubot != null) { this.mBlaubot.getConnectionStateMachine().removeConnectionStateMachineListener(mBlaubotConnectionListener); this.mBlaubot = null; } // force some updates updateUI(); } }