package vandy.mooc;
import java.util.Locale;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;
/**
* Main Activity for the Android implementation of the concurrent
* ping-pong application.
*/
public class MainActivity extends Activity {
/**
* A plain TextView that PingPong will be "played" upon.
*/
private TextView mPingPongTextViewLog;
/**
* A ScrollView that contains the PingPong results.
*/
private ScrollView mPingPongScrollView;
/**
* A more colorful TextView that prints "Ping" or "Pong" to the
* display.
*/
private TextView mPingPongColorOutput;
/**
* Button that allows playing and resetting the concurrent
* ping/pong algorithm.
*/
private Button mPlayButton;
/**
* Possible states that the ping/pong program can be in.
*/
enum ProgramState {
/**
* The program is ready to run.
*/
RUN,
/**
* The program is running but can be reset.
*/
RESET
};
/**
* The program's current state.
*/
private ProgramState mProgramState = ProgramState.RUN;
/**
* Defines the strategy for outputting text to the display.
*/
private OutputStrategy mOutputStrategy;
/**
* Maximum number of times to play ping-pong.
*/
private int mMaxIterations = 10;
/**
* PlayPingPong object.
*/
private PlayPingPong mPlayPingPong;
/**
* Hook method called when the Activity is first launched.
*/
protected void onCreate(Bundle savedInstanceState) {
// Call up to the super class to perform initializations.
super.onCreate(savedInstanceState);
// Sets the content view to the xml file, activity_ping_pong.
setContentView(R.layout.activity_ping_pong);
// Cache various TextView and Button widgets used to interact
// with the user.
mPingPongTextViewLog =
(TextView) findViewById(R.id.pingpong_text_output);
mPingPongScrollView =
(ScrollView) findViewById(R.id.scrollview_text_output);
mPingPongColorOutput =
(TextView) findViewById(R.id.pingpong_color_output);
mPlayButton =
(Button) findViewById(R.id.play_button);
// Create a new OutputStrategy that displays the ping and pong
// output to the user.
mOutputStrategy = new OutputStrategy(this);
}
/**
* Sets the action of the button on click state.
*/
public void playButtonClicked(View view) {
if (mProgramState == null) {
// Notify the player that something has gone wrong and
// reset.
mOutputStrategy.errorLog("MainActivity",
"encountered a null state, "
+ "which was then set to RESET");
mProgramState = ProgramState.RESET;
}
switch(mProgramState) {
case RUN:
// Create the object that plays ping-pong.
mPlayPingPong = new PlayPingPong(mMaxIterations,
mOutputStrategy);
// Create and start a background thread that uses the
// Android HaMeR concurrency framework to run calls to
// print() on the UI thread after a short 0.5 sec delay.
mDelayedOutputThread =
new DelayedOutputThread(mPlayPingPong);
mDelayedOutputThread.start();
mPlayButton.setText(R.string.reset_button);
mPingPongScrollView.fullScroll(ScrollView.FOCUS_UP);
mProgramState = ProgramState.RESET;
break;
case RESET:
// Stop the thread that handles calls to print();
mDelayedOutputThread.interrupt();
// Reset the color output.
mPingPongColorOutput.setText("");
mPingPongColorOutput.setBackgroundColor(Color.TRANSPARENT);
// Empty TextView and prepare the UI to start another run
// of the concurrent ping/pong algorithm.
mPingPongTextViewLog.setText(R.string.empty_string);
mPlayButton.setText(R.string.play_button);
mProgramState = ProgramState.RUN;
break;
}
}
/**
* Instance of DelayedOutputThread that's described below.
*/
private DelayedOutputThread mDelayedOutputThread;
/*
* Defines a HandlerThread that waits 0.5 seconds between handling
* messages so the "ping" and "pong" output is visually
* discernable by the user.
*/
class DelayedOutputThread extends HandlerThread {
/**
* Handler that's used to post Runnables to the
* HandlerThread's Looper.
*/
private volatile Handler mDelayedOutputHandler;
/**
* Completion hook that's dispatched once the Handler is
* initialized.
*/
private final Runnable mCompletionHook;
/**
* Constructor initializes the super class and completion hook.
*/
DelayedOutputThread(Runnable completionHook) {
super ("DelayedOutputThread");
mCompletionHook = completionHook;
}
/**
* Hook method called back by HandlerThread.run() after the
* Looper is initialized.
*/
protected void onLooperPrepared() {
// Create the Handler in the context of the
// HandlerThread's Looper.
mDelayedOutputHandler = new Handler();
// Start a Thread to run the mRunnable.
new Thread(mCompletionHook).start();
}
/**
* Run the specified command in the context of the
* HandlerThread's Looper.
*/
public void runOnDelayedOutputThread(Runnable command) {
mDelayedOutputHandler.post(command);
}
}
/**
* Prints the output string to the text log on screen. If the
* string contains "ping" (case-insensitive) then a large Ping!
* will be shown on screen with a certain color. The same goes for
* strings containing "pong".
*
* This method is called from a background thread and will not
* block the caller. However, the code to display this output will
* be posted to the UI thread in such a way that any changes to
* the UI will be spaced out by 0.5 seconds, thereby giving the
* user an appropriate amount of time to appreciate the ping'ing
* and the pong'ing that is happening.
*/
public void print(final String output) {
// Post a Runnable task that prints the output with a 0.5
// second delay between displaying the output.
mDelayedOutputThread.runOnDelayedOutputThread(new Runnable() {
@Override
public void run() {
// Post a Runnable whose run() method instructs the UI
// to print the output.
runOnUiThread(new Runnable() {
@Override
public void run() {
mPingPongTextViewLog.append(output);
mPingPongScrollView.fullScroll(ScrollView.FOCUS_DOWN);
// If we encounter a ping, throw it up on the
// screen in color.
if (output.toLowerCase(Locale.US).contains("ping")) {
mPingPongColorOutput.setBackgroundColor(Color.WHITE);
mPingPongColorOutput.setTextColor(Color.BLACK);
mPingPongColorOutput.setText("PING");
}
else if (output.toLowerCase(Locale.US).contains("pong")) {
mPingPongColorOutput.setBackgroundColor(Color.BLACK);
mPingPongColorOutput.setTextColor(Color.WHITE);
mPingPongColorOutput.setText("PONG");
}
}
});
// Wait 0.5 seconds before handling the next message.
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// If we get interrupted, stop the looper.
Looper.myLooper().quit();
}
}
});
}
}