diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 09f977e..32e0605 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,24 +1,25 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/psrivastava/stopwatch/Chronometer.java b/src/com/psrivastava/stopwatch/Chronometer.java index 066e738..0adbe6f 100644 --- a/src/com/psrivastava/stopwatch/Chronometer.java +++ b/src/com/psrivastava/stopwatch/Chronometer.java @@ -1,156 +1,170 @@ -package com.psrivastava.stopwatch; - -import java.text.DecimalFormat; - -import android.content.Context; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.widget.TextView; - -/* Customized version of Chronometer class - * Original source from Antonis Balasas - * https://github.com/antoniom/Millisecond-Chronometer - */ -public class Chronometer extends TextView { - private static final String TAG = "Chronometer"; - - public interface OnChronometerTickListener { - - void onChronometerTick(Chronometer chronometer); - } - - private long mBase; - private boolean mVisible; - private boolean mStarted; - private boolean mRunning; - private OnChronometerTickListener mOnChronometerTickListener; - - private static final int TICK_WHAT = 2; - - public Chronometer(Context context) { - this(context, null, 0); - } - - public Chronometer(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public Chronometer(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - init(); - } - - private void init() { - mBase = SystemClock.elapsedRealtime(); - updateText(mBase); - } - - public void setBase(long base) { - mBase = base; - dispatchChronometerTick(); - updateText(SystemClock.elapsedRealtime()); - } - - public long getBase() { - return mBase; - } - - public void setOnChronometerTickListener(OnChronometerTickListener listener) { - mOnChronometerTickListener = listener; - } - - public OnChronometerTickListener getOnChronometerTickListener() { - return mOnChronometerTickListener; - } - - public void start() { - mStarted = true; - updateRunning(); - } - - public boolean isStarted() { - return mStarted; - } - - public void stop() { - mStarted = false; - updateRunning(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mVisible = false; - updateRunning(); - } - - @Override - protected void onWindowVisibilityChanged(int visibility) { - super.onWindowVisibilityChanged(visibility); - mVisible = visibility == VISIBLE; - updateRunning(); - } - - private synchronized void updateText(long now) { - long timeElapsed = now - mBase; - - DecimalFormat df = new DecimalFormat("00"); - - int hours = (int) (timeElapsed / (3600 * 1000)); - int remaining = (int) (timeElapsed % (3600 * 1000)); - - int minutes = (int) (remaining / (60 * 1000)); - remaining = (int) (remaining % (60 * 1000)); - - int seconds = (int) (remaining / 1000); - remaining = (int) (remaining % (1000)); - - int milliseconds = (int) (((int) timeElapsed % 1000) / 100); - - String text = ""; - - if (hours > 0) { - text += df.format(hours) + ":"; - } - - text += df.format(minutes) + ":"; - text += df.format(seconds) + "."; - text += Integer.toString(milliseconds); - - setText(text); - } - - private void updateRunning() { - boolean running = mVisible && mStarted; - if (running != mRunning) { - if (running) { - updateText(SystemClock.elapsedRealtime()); - dispatchChronometerTick(); - mHandler.sendMessageDelayed( - Message.obtain(mHandler, TICK_WHAT), 100); - } else { - mHandler.removeMessages(TICK_WHAT); - } - mRunning = running; - } - } - - private Handler mHandler = new Handler() { - public void handleMessage(Message m) { - if (mRunning) { - updateText(SystemClock.elapsedRealtime()); - dispatchChronometerTick(); - sendMessageDelayed(Message.obtain(this, TICK_WHAT), 100); - } - } - }; - - void dispatchChronometerTick() { - if (mOnChronometerTickListener != null) { - mOnChronometerTickListener.onChronometerTick(this); - } - } +package com.psrivastava.stopwatch; + +import java.text.DecimalFormat; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.TextView; + +/* Gets time from service and displays it. + * Original source from Antonis Balasas + * https://github.com/antoniom/Millisecond-Chronometer + */ +public class Chronometer extends TextView { + private static final String TAG = "Chronometer"; + + private boolean mRunning; + + private static final int TICK_WHAT = 2; + + IChronometerService mService; + + private ServiceConnection mConnection = new ServiceConnection() { + + public void onServiceDisconnected(ComponentName name) { + stopDisplay(); + } + + public void onServiceConnected(ComponentName name, IBinder service) { + mService = IChronometerService.Stub.asInterface(service); + Log.v(TAG, "Service Connected"); + updateText(); + startDisplay(); + } + }; + + public Chronometer(Context context) { + this(context, null, 0); + } + + public Chronometer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public Chronometer(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + context.getApplicationContext().bindService(new Intent(context, ChronometerService.class), mConnection, Context.BIND_AUTO_CREATE); + } + + public void start() { + try { + mService.start(); + } catch (RemoteException e) { + Log.e(TAG, "Connection to service failed.", e); + } + startDisplay(); + } + + public void pause() { + try { + mService.pause(); + } catch (RemoteException e) { + Log.e(TAG, "Connection to service failed.", e); + } + stopDisplay(); + updateText(); + } + + public void resume() { + try { + mService.resume(); + } catch (RemoteException e) { + Log.e(TAG, "Connection to service failed.", e); + } + startDisplay(); + } + + public void stop() { + try { + mService.stop(); + } catch (RemoteException e) { + Log.e(TAG, "Connection to service failed.", e); + } + stopDisplay(); + updateText(); + } + + @Override + protected void onDetachedFromWindow() { + stopDisplay(); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + if (visibility == VISIBLE) { + startDisplay(); + } + else { + stopDisplay(); + } + } + + /** + * Display elapsed time. + */ + private synchronized void updateText() { + + try { + long timeElapsed; + timeElapsed = mService.getTime(); + + DecimalFormat df = new DecimalFormat("00"); + + int hours = (int) (timeElapsed / (3600 * 1000)); + int remaining = (int) (timeElapsed % (3600 * 1000)); + + int minutes = (int) (remaining / (60 * 1000)); + remaining = (int) (remaining % (60 * 1000)); + + int seconds = (int) (remaining / 1000); + remaining = (int) (remaining % (1000)); + + int milliseconds = (int) (((int) timeElapsed % 1000) / 100); + + String text = ""; + + if (hours > 0) { + text += df.format(hours) + ":"; + } + + text += df.format(minutes) + ":"; + text += df.format(seconds) + "."; + text += Integer.toString(milliseconds); + + setText(text); + } catch (RemoteException e) { + Log.e(TAG, "Connection to service failed.", e); + } + } + + private void startDisplay() { + try { + if (!mRunning && mService != null && mService.isRunning()) { + mHandler.dispatchMessage(Message.obtain(mHandler, TICK_WHAT)); + mRunning = true; + } + } catch (RemoteException e) { + Log.e(TAG, "Connection to service failed.", e); + } + } + + private void stopDisplay() { + mHandler.removeMessages(TICK_WHAT); + mRunning = false; + } + + private Handler mHandler = new Handler() { + public void handleMessage(Message m) { + sendMessageDelayed(Message.obtain(this, TICK_WHAT), 100); + updateText(); + } + }; } \ No newline at end of file diff --git a/src/com/psrivastava/stopwatch/ChronometerService.java b/src/com/psrivastava/stopwatch/ChronometerService.java new file mode 100644 index 0000000..fbada00 --- /dev/null +++ b/src/com/psrivastava/stopwatch/ChronometerService.java @@ -0,0 +1,86 @@ +package com.psrivastava.stopwatch; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +/** + * Takes the time, makes sure this works even when the main activity is closed. + */ +public class ChronometerService extends Service { + + private static final String TAG = "ChronometerService"; + + private long mBaseTime = SystemClock.elapsedRealtime(); + private long mPausedTime = 0; + private boolean mRunning = false; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private IBinder mBinder = new IChronometerService.Stub() { + + /** + * Starts the timer. + */ + public void start() throws RemoteException { + mBaseTime = SystemClock.elapsedRealtime(); + mPausedTime = 0; + mRunning = true; + } + + /** + * Pauses the timer, to start again use {@link #resume()} + */ + public void pause() throws RemoteException { + mPausedTime = SystemClock.elapsedRealtime(); + mRunning = false; + } + + /** + * Continues running after pausing the timer with {@link #pause()} + */ + public void resume() throws RemoteException { + mBaseTime = SystemClock.elapsedRealtime() - (mPausedTime - mBaseTime); + mPausedTime = 0; + mRunning = true; + } + + /** + * Resets timer to zero. + */ + public void stop() throws RemoteException { + mBaseTime = 0; + mPausedTime = 0; + mRunning = false; + } + + /** + * Has neither {@link #pause()} nor {@link #stop()} been called since + * last calling {@link #start()}? + * + * @return True if the timer is running + */ + public boolean isRunning() throws RemoteException { + return mRunning; + } + + /** + * Gets the time that should be displayed on screen. + * + * @return Time for display in milliseconds + */ + public long getTime() throws RemoteException { + return (mRunning) + ? SystemClock.elapsedRealtime() - mBaseTime + : (mPausedTime != 0) + ? mPausedTime - mBaseTime + : 0; + } + }; +} \ No newline at end of file diff --git a/src/com/psrivastava/stopwatch/IChronometerService.aidl b/src/com/psrivastava/stopwatch/IChronometerService.aidl new file mode 100644 index 0000000..ad654b6 --- /dev/null +++ b/src/com/psrivastava/stopwatch/IChronometerService.aidl @@ -0,0 +1,10 @@ +package com.psrivastava.stopwatch; + +interface IChronometerService { // + void start(); + void stop(); + boolean isRunning(); + long getTime(); + void pause(); + void resume(); +} \ No newline at end of file diff --git a/src/com/psrivastava/stopwatch/StopwatchActivity.java b/src/com/psrivastava/stopwatch/StopwatchActivity.java index 0c21a2b..f6fe24b 100644 --- a/src/com/psrivastava/stopwatch/StopwatchActivity.java +++ b/src/com/psrivastava/stopwatch/StopwatchActivity.java @@ -1,128 +1,154 @@ -package com.psrivastava.stopwatch; - -import android.app.Activity; -import android.os.Bundle; -import android.os.SystemClock; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class StopwatchActivity extends Activity { - /** Called when the activity is first created. */ - private static final String TAG = "StopwatchActivity"; - - Chronometer mChronometer; - Boolean mChronoPaused = false; - long mElapsedTime = 0; - LinearLayout buttonContainer; - ImageButton mStartButton, mPauseButton, mStopButton; - int mLapCount = 0; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - buttonContainer = (LinearLayout) findViewById(R.id.llButtonContainer); - - mChronometer = (Chronometer) findViewById(R.id.chronometer); - - mStartButton = (ImageButton) findViewById(R.id.bStart); - mStartButton.setOnClickListener(startListener); - - mPauseButton = (ImageButton) findViewById(R.id.bPause); - mPauseButton.setOnClickListener(pauseListener); - - mStopButton = (ImageButton) findViewById(R.id.bStop); - mStopButton.setOnClickListener(stopListener); - - } - - View.OnClickListener startListener = new OnClickListener() { - public void onClick(View v) { - if (mChronoPaused) { - Log.v(TAG, "start-chrono was paused"); - buttonContainer.setVisibility(View.VISIBLE); - mChronometer.setBase(SystemClock.elapsedRealtime() - - mElapsedTime); - } else if (!mChronometer.isStarted()) { - Log.v(TAG, "start-chrono was stopped"); - buttonContainer.setVisibility(View.VISIBLE); - mChronometer.setBase(SystemClock.elapsedRealtime()); - } else if (!mChronoPaused) { - Log.v(TAG, "split button pressed"); - LinearLayout history = (LinearLayout) findViewById(R.id.llLaps); - TextView lap = new TextView(getApplicationContext()); - mLapCount++; - lap.setText(mLapCount - + "." - + timeFormat((SystemClock.elapsedRealtime() - mChronometer - .getBase()))); - history.addView(lap, 0); - } - - mChronometer.start(); - mStartButton.setImageResource(R.drawable.split); - mChronoPaused = false; - } - - private String timeFormat(long l) { - int minutes; - float seconds; - int milliseconds; - String mins; - String secs; - - float time = (float) l / 1000; - - minutes = (int) (time / 60); - seconds = (time % 60); - milliseconds = (int) (((int) l % 1000) / 100); - - if (minutes < 10) { - mins = "0" + minutes; - } else { - mins = "" + minutes; - } - - if (seconds < 10) { - secs = "0" + (int) seconds; - } else { - secs = "" + (int) seconds; - } - - return "\t\t\t" + mins + ":" + secs + "." + milliseconds; - } - }; - - View.OnClickListener pauseListener = new OnClickListener() { - public void onClick(View v) { - if (!mChronoPaused) { - Log.v(TAG, "pause"); - mChronometer.stop(); - mElapsedTime = SystemClock.elapsedRealtime() - - mChronometer.getBase(); - mChronoPaused = true; - mStartButton.setImageResource(R.drawable.start); - } - } - }; - - View.OnClickListener stopListener = new OnClickListener() { - public void onClick(View v) { - Log.v(TAG, "stop"); - mChronometer.stop(); - buttonContainer.setVisibility(View.INVISIBLE); - mChronometer.setBase(SystemClock.elapsedRealtime()); - LinearLayout history = (LinearLayout) findViewById(R.id.llLaps); - history.removeAllViews(); - mStartButton.setImageResource(R.drawable.start); - mChronoPaused = false; - mLapCount = 0; - } - }; - +package com.psrivastava.stopwatch; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class StopwatchActivity extends Activity { + + private static final String TAG = "StopwatchActivity"; + + private Chronometer mChronometer; + private LinearLayout buttonContainer; + private ImageButton mStartButton, mPauseButton, mStopButton; + + private int mLapCount = 0; + private Boolean mChronoPaused = false; + + private IChronometerService mService; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + buttonContainer = (LinearLayout) findViewById(R.id.llButtonContainer); + + mChronometer = (Chronometer) findViewById(R.id.chronometer); + + mStartButton = (ImageButton) findViewById(R.id.bStart); + mStartButton.setOnClickListener(startListener); + + mPauseButton = (ImageButton) findViewById(R.id.bPause); + mPauseButton.setOnClickListener(pauseListener); + + mStopButton = (ImageButton) findViewById(R.id.bStop); + mStopButton.setOnClickListener(stopListener); + + bindService(new Intent(this, ChronometerService.class), mConnection, Context.BIND_AUTO_CREATE); + } + + private ServiceConnection mConnection = new ServiceConnection() { + + public void onServiceDisconnected(ComponentName name) { + } + + public void onServiceConnected(ComponentName name, IBinder service) { + mService = IChronometerService.Stub.asInterface(service); + Log.v(TAG, "Service Connected"); + try { + if (mService.isRunning()) { + buttonContainer.setVisibility(View.VISIBLE); + mStartButton.setImageResource(R.drawable.split); + mChronoPaused = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Connection to service failed.", e); + } + } + }; + + View.OnClickListener startListener = new OnClickListener() { + public void onClick(View v) { + try { + if (mChronoPaused) { + Log.v(TAG, "start-chrono was paused"); + mChronometer.resume(); + } + else if (!mService.isRunning()) { + Log.v(TAG, "start-chrono was stopped"); + mChronometer.start(); + } + else if (!mChronoPaused) { + Log.v(TAG, "split button pressed"); + LinearLayout history = (LinearLayout) findViewById(R.id.llLaps); + TextView lap = new TextView(getApplicationContext()); + mLapCount++; + lap.setText(mLapCount + + "." + + timeFormat(mService.getTime())); + history.addView(lap, 0); + } + + buttonContainer.setVisibility(View.VISIBLE); + mStartButton.setImageResource(R.drawable.split); + mChronoPaused = false; + } catch (RemoteException e) { + Log.e(TAG, "Connection to service failed.", e); + } + } + + private String timeFormat(long l) { + int minutes; + float seconds; + int milliseconds; + String mins; + String secs; + + float time = (float) l / 1000; + + minutes = (int) (time / 60); + seconds = (time % 60); + milliseconds = (int) (((int) l % 1000) / 100); + + if (minutes < 10) { + mins = "0" + minutes; + } else { + mins = "" + minutes; + } + + if (seconds < 10) { + secs = "0" + (int) seconds; + } else { + secs = "" + (int) seconds; + } + + return "\t\t\t" + mins + ":" + secs + "." + milliseconds; + } + }; + + View.OnClickListener pauseListener = new OnClickListener() { + public void onClick(View v) { + if (!mChronoPaused) { + Log.v(TAG, "pause"); + mChronometer.pause(); + mChronoPaused = true; + mStartButton.setImageResource(R.drawable.start); + } + } + }; + + View.OnClickListener stopListener = new OnClickListener() { + public void onClick(View v) { + Log.v(TAG, "stop"); + buttonContainer.setVisibility(View.INVISIBLE); + mChronometer.stop(); + LinearLayout history = (LinearLayout) findViewById(R.id.llLaps); + history.removeAllViews(); + mStartButton.setImageResource(R.drawable.start); + mChronoPaused = false; + mLapCount = 0; + } + }; } \ No newline at end of file