package com.hyperling.apps.infinitetimer; import android.app.NotificationManager; import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Color; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.SeekBar; import android.widget.TableRow; import android.widget.TextView; import java.net.URI; import java.util.Map; public class MainActivity extends AppCompatActivity { String TAG = "MainActivity.", keySharedPreferences, keyDebug, keyChronometerTime, keyLoopInterval, keyStartStop, keyAppOpen, keyServiceRunning, stringChronometerDefault, stringChronometerTime, stringLoopInterval; // Try to make methods send and accept parameters to eliminate some of these, same with the string* Strings above long longLoopInterval, longStartTime, longEndTime, longTimeLeft; // Interrupt based checking would be nice int waitInterval = 99; boolean DEBUG, start, appOpen, serviceRunning; SharedPreferences sharedPreferences; TextView chronometer; Button btnStartStop, btnChooseTime, btnResetPause; EditText etHours, etMinutes, etSeconds, etMillis; TableRow trTime; TextView tvDot; Uri ringtoneUri; MediaPlayer mediaPlayer; SeekBar seekBar; AudioManager audioManager; TextView tvSeekBar; @Override protected void onCreate(Bundle savedInstanceState) { String tag = "onCreate"; if (DEBUG) Log.d(tag, "Starting"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Prevent the keyboard from popping up automatically this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); /***** Get Strings *****/ keySharedPreferences = getString(R.string.keySharedPreferences); keyDebug = getString(R.string.keyDebug); keyChronometerTime = getString(R.string.keyChronometerTime); keyLoopInterval = getString(R.string.keyLoopInterval); keyStartStop = getString(R.string.keyStartStop); keyAppOpen = getString(R.string.keyAppOpen); keyServiceRunning = getString(R.string.keyServiceRunning); stringChronometerDefault = getString(R.string.chronometerDefault); if (DEBUG) Log.d(tag, "Got strings."); /***** Shared Preferences *****/ sharedPreferences = getSharedPreferences(keySharedPreferences, MODE_PRIVATE); DEBUG = sharedPreferences.getBoolean(keyDebug, true); appOpen = sharedPreferences.getBoolean(keyAppOpen, false); serviceRunning = sharedPreferences.getBoolean(keyServiceRunning, false); if (DEBUG) Log.d(tag, "Got preferences."); // Sound ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); if (DEBUG) Log.d(tag, "ringtoneUri=" + ringtoneUri.toString()); mediaPlayer = MediaPlayer.create(this, ringtoneUri); if (mediaPlayer == null) { if (DEBUG) Log.d(tag, "Media Player is null, trying alarm"); ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); mediaPlayer = MediaPlayer.create(this, ringtoneUri); } if (mediaPlayer == null) { if (DEBUG) Log.d(tag, "Media Player is null, trying ringtone"); ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); mediaPlayer = MediaPlayer.create(this, ringtoneUri); } if (mediaPlayer == null) { if (DEBUG) Log.d(tag, "Media Player is null, trying all"); ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALL); mediaPlayer = MediaPlayer.create(this, ringtoneUri); } if (mediaPlayer == null) { if (DEBUG) Log.d(tag, "Media Player is null, trying local sound byte"); mediaPlayer = MediaPlayer.create(this, R.raw.chime_sound_7143); } if (mediaPlayer == null) { String message = "Could not find a notification, alarm, or ringtone to play."; message += " App will not be able to function without one of these."; message += " Please exit and install a system notification sound."; new AlertDialog.Builder(MainActivity.this) .setTitle("Error") .setMessage(message) .setPositiveButton("Exit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); finish(); } }) .setCancelable(false) .show() ; } else { if (DEBUG) Log.d(tag, "mediaPlayer=" + mediaPlayer.toString()); } if (DEBUG){ // Print all preferences Map sharedPreferencesAll = sharedPreferences.getAll(); for (String key : sharedPreferencesAll.keySet()){ String value = sharedPreferencesAll.get(key).toString(); Log.d(tag, "key=" + key + " value=" + value); // If app crashed, ensure buttons are correct. if (key.equals(keyStartStop) && value.equals("true")) { if (DEBUG) Log.d(tag, "Shutting off improper START value"); // Ensure START is always off if we are creating (not resuming). int count = 0; while (!sharedPreferences.edit().putBoolean(keyStartStop, false).commit() && count < 50) { if (DEBUG) Log.d(tag, "Commit failed, trying again. count=" + count); count++; } boolean checkStartStop = sharedPreferences.getBoolean(keyStartStop, false);; Log.d(tag, "key=" + key + " value=" + checkStartStop); } } } /***** UI *****/ chronometer = (TextView) findViewById(R.id.chronometer); chronometer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { doTimeChooser(); } }); btnChooseTime = (Button) findViewById(R.id.btnChooseTime); btnChooseTime.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { doTimeChooser(); } }); btnStartStop = (Button) findViewById(R.id.btnStartStop); btnStartStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Button b = (Button) v; startStopChronometer(b.getText().toString()); } }); btnResetPause = (Button) findViewById(R.id.btnResetPause); btnResetPause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { resetPauseChronometer(); } }); etHours = (EditText) findViewById(R.id.etHours); etMinutes = (EditText) findViewById(R.id.etMinutes); etSeconds = (EditText) findViewById(R.id.etSeconds); etMillis = (EditText) findViewById(R.id.etMillis); trTime = (TableRow) findViewById(R.id.trTime); trTime.setWeightSum(3); tvDot = (TextView) findViewById(R.id.tvDot); tvDot.setEnabled(false); tvDot.setVisibility(View.GONE); etMillis.setEnabled(false); etMillis.setVisibility(View.GONE); //(ViewManager) etMillis.getParent().remove seekBar = findViewById(R.id.seekBar); setVolumeControlStream(AudioManager.STREAM_MUSIC); audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); seekBar.setMax(maxVolume); tvSeekBar = findViewById(R.id.tvSeekbar); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, i, AudioManager.FLAG_SHOW_UI); syncVolume(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { syncVolume(); } @Override public void onStopTrackingTouch(SeekBar seekBar) { syncVolume(); } }); if (DEBUG) Log.d(tag, "Finished"); } private void doTimeChooser(){ String tag = TAG + "doTimeChooser"; if (DEBUG) Log.d(tag, "Starting"); //stringLoopInterval = "00:00:07"; //setEditTexts(stringLoopInterval); if (DEBUG) Log.d(tag, "Finished"); } private void setEditTexts(String interval){ String tag = TAG + "setEditTexts"; if (DEBUG) Log.d(tag, "Starting, interval=" + interval); if (!interval.equals(stringChronometerDefault)) { String[] time = interval.split(":"); for (int i = 0; i < time.length; i++){ while (time[i].length() < 2){ time[i] = "0" + time[i]; } } if (time.length == 3) { etHours.setText(time[0]); etMinutes.setText(time[1]); etSeconds.setText(time[2]); } } else{ etHours.setText(""); etMinutes.setText(""); etSeconds.setText(""); } setLoopInterval(); if (DEBUG) Log.d(tag, "Finished"); } private void setLoopInterval(){ String tag = TAG + "setLoopInterval"; if (DEBUG) Log.d(tag, "Starting"); int hours = 0, minutes = 0, seconds = 0; try { hours = Integer.parseInt("0" + etHours.getText().toString()); } catch (Exception e) { if (DEBUG) Log.d(tag, "Hours had an exception, using highest value"); hours = 99; } try { minutes = Integer.parseInt("0" + etMinutes.getText().toString()); } catch (Exception e) { if (DEBUG) Log.d(tag, "Minutes had an exception, using highest value"); minutes = 59; } try { seconds = Integer.parseInt("0" + etSeconds.getText().toString()); } catch (Exception e) { if (DEBUG) Log.d(tag, "Seconds had an exception, using highest value"); seconds = 59; } if ((hours + (minutes/60) + (seconds/60/60)) > 99) { if (DEBUG) Log.d(tag, "Sum is over 99 hours, setting to 99:59:59"); hours = 99; minutes = 59; seconds = 59; } if (DEBUG) Log.d(tag, "hours=" + hours); if (DEBUG) Log.d(tag, "minutes=" + minutes); if (DEBUG) Log.d(tag, "seconds=" + seconds); if (hours > 0) etHours.setText("" + hours); if (minutes > 0) etMinutes.setText("" + minutes); if (seconds > 0) etSeconds.setText("" + seconds); longLoopInterval = (((hours*60*60) + (minutes*60) + seconds) * 1000); if (DEBUG) Log.d(tag, "longLoopInterval=" + longLoopInterval); setChronometer(longLoopInterval); if (DEBUG) Log.d(tag, "Finished"); } private void setChronometer(long milliseconds){ String tag = TAG + "setChronometer"; if (DEBUG) Log.d(tag, "Starting, milliseconds=" + milliseconds); //chronometer = (TextView) findViewById(R.id.chronometer); String[] time = { Long.toString(milliseconds/(1000*60*60)), Long.toString((milliseconds/(1000*60))%60), Long.toString((milliseconds/(1000))%60) }; for (int i = 0; i < time.length; i++){ while (time[i].length() < 2){ time[i] = "0" + time[i]; } } String display = time[0] + ":" + time[1] + ":" + time[2]; // Testing orientation change not allowing chronometer to update /* chronometer = (TextView) findViewById(R.id.chronometer); chronometer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { doTimeChooser(); } });*/ if (DEBUG) Log.d(tag, "display=" + display); sharedPreferences.edit().putString(keyChronometerTime, display).apply(); if (DEBUG) Log.d(tag, "Before=" + chronometer.getText()); chronometer.setText(display); if (DEBUG) Log.d(tag, "After=" + chronometer.getText()); if (DEBUG) Log.d(tag, "Finished"); } private void startStopChronometer(String buttonText){ String tag = TAG + "startStopChronometer"; if (DEBUG) Log.d(tag, "Starting"); start = !start; SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean(keyStartStop, start); editor.putBoolean(keyAppOpen, true); editor.putBoolean(keyServiceRunning, false); editor.apply(); // hours:minutes:seconds should be disabled during count down. flipInputTexts(); if (buttonText.equals(getString(R.string.btnStart))){ incrementTime.sendEmptyMessage(0); setLoopInterval(); resetTimeLeft(); setStartTime(); } else if (buttonText.equals(getString(R.string.btnPause))){ btnStartStop.setText(getString(R.string.btnResume)); btnResetPause.setEnabled(false); etHours.setEnabled(false); etMinutes.setEnabled(false); etSeconds.setEnabled(false); } else if (buttonText.equals(getString(R.string.btnStop))){ setChronometer(longLoopInterval); } else if (buttonText.equals(getString(R.string.btnResume))){ setStartTime(); incrementTime.sendEmptyMessage(0); btnResetPause.setEnabled(true); } flipStartStopButton(); flipResetPauseButton(); if (DEBUG) Log.d(tag, "Finished"); } private void recoverScreen(){ String tag = TAG + "recoverScreen"; if (DEBUG) Log.d(tag, "Starting"); /***** Retrieve the last settings *****/ // Chronometer stringChronometerTime = sharedPreferences.getString(keyChronometerTime, stringChronometerDefault); chronometer.setText(stringChronometerTime); // Loop interval stringLoopInterval = sharedPreferences.getString(keyLoopInterval, stringChronometerDefault); setEditTexts(stringLoopInterval); // Start/Stop Button start = sharedPreferences.getBoolean(keyStartStop, false); if (DEBUG) Log.d(tag, "start=" + start); flipStartStopButton(); flipResetPauseButton(); flipInputTexts(); // Testing how to keep timer ticking after rotation //start = !start; //startStopChronometer(stringChronometerTime); //incrementTime.sendEmptyMessage(0); if (DEBUG) Log.d(tag, "Finished"); } private void flipStartStopButton(){ String tag = TAG + "flipStartStopButton"; if (DEBUG) Log.d(tag, "Starting"); start = sharedPreferences.getBoolean(keyStartStop, false); serviceRunning = sharedPreferences.getBoolean(keyServiceRunning, false); //if (start && !serviceRunning){ if (start){ btnStartStop.setText(getString(R.string.btnStop)); btnStartStop.setBackgroundColor(Color.RED); } else { if (!btnStartStop.getText().toString().equals(getString(R.string.btnResume))) { btnStartStop.setText(getString(R.string.btnStart)); } btnStartStop.setBackgroundColor(Color.GREEN); } if (DEBUG) Log.d(tag, "Finished"); } private void resetPauseChronometer(){ String tag = TAG + "resetPauseChronometer"; if (DEBUG) Log.d(tag, "Starting"); start = sharedPreferences.getBoolean(keyStartStop, false); serviceRunning = sharedPreferences.getBoolean(keyServiceRunning, false); //if (start && !serviceRunning){ if (start){ startStopChronometer(btnResetPause.getText().toString()); } else{ setEditTexts(stringChronometerDefault); } if (DEBUG) Log.d(tag, "Finished"); } private void flipResetPauseButton(){ String tag = TAG + "flipResetPauseButton"; if (DEBUG) Log.d(tag, "Starting"); start = sharedPreferences.getBoolean(keyStartStop, false); serviceRunning = sharedPreferences.getBoolean(keyServiceRunning, false); //if (start && !serviceRunning){ if (start){ btnResetPause.setText(getString(R.string.btnPause)); btnResetPause.setBackgroundColor(Color.YELLOW); } else { btnResetPause.setText(getString(R.string.btnReset)); btnResetPause.setBackgroundColor(Color.RED); } if (DEBUG) Log.d(tag, "Finished"); } private final Handler incrementTime = new Handler(){ @Override public void handleMessage(Message msg) { String tag = TAG + "handleMessage"; if (DEBUG) Log.d(tag, "Starting"); super.handleMessage(msg); appOpen = sharedPreferences.getBoolean(keyAppOpen, true); if (appOpen) { sharedPreferences.edit().putBoolean(keyServiceRunning, false).apply(); } else { sharedPreferences.edit().putBoolean(keyServiceRunning, true).apply(); } start = sharedPreferences.getBoolean(keyStartStop, false); if (start){ //start = !start; //sharedPreferences.edit().putBoolean(keyStartStop, false).apply(); // Calculate new time and text setTimeLeft(); if (DEBUG) Log.d(tag, "longTimeLeft=" + longTimeLeft); // Check if we need to beep if (longTimeLeft < 1000){ playSound(); resetTimeLeft(); setStartTime(); if (DEBUG) { Log.d(tag, "longLoopInterval=" + longLoopInterval); Log.d(tag, "longStartTime=" + longStartTime); Log.d(tag, "longEndTime=" + longEndTime); Log.d(tag, "longTimeLeft=" + longTimeLeft); Log.d(tag, "waitInterval=" + waitInterval); Log.d(tag, "start=" + start); //chronometer.setText("Test"); //setChronometer(200); /* start=false; startStopChronometer("Start"); */ } } // Wait Runnable task = new Runnable() { @Override public void run() { synchronized (this){ try{ wait(waitInterval); if (DEBUG) Log.d(tag, "Done waiting."); } catch (Exception e){ e.printStackTrace(); if (DEBUG) Log.d(tag, "Failed to wait."); } } //sharedPreferences.edit().putBoolean(keyStartStop, true).apply(); incrementTime.sendEmptyMessage(0); //startStopChronometer("Start"); } }; Thread t = new Thread(task); t.start(); } if (DEBUG) Log.d(tag, "Finished"); } }; private void setStartTime(){ String tag = TAG + "setStartTime"; if (DEBUG) Log.d(tag, "Starting"); if (DEBUG) Log.d(tag, "System.currentTimeMillis()=" + System.currentTimeMillis()); if (longTimeLeft == 0) { if (DEBUG) Log.d(tag, "longTimeLeft=" + longTimeLeft); // Add extra 1000 so timer starts on interval and beeps after 1 longStartTime = System.currentTimeMillis() + 1000 - waitInterval; if (DEBUG) Log.d(tag, "longStartTime=" + longStartTime); longEndTime = longStartTime + longLoopInterval; if (DEBUG) Log.d(tag, "longEndTime=" + longEndTime); } else { if (DEBUG) Log.d(tag, "longTimeLeft=" + longTimeLeft); longStartTime = System.currentTimeMillis() - waitInterval; if (DEBUG) Log.d(tag, "longStartTime=" + longStartTime); longEndTime = longStartTime + longTimeLeft; if (DEBUG) Log.d(tag, "longEndTime=" + longEndTime); } if (DEBUG) Log.d(tag, "Finished"); } private void setTimeLeft(){ String tag = TAG + "setTimeLeft"; if (DEBUG) Log.d(tag, "Starting"); longTimeLeft = longEndTime - System.currentTimeMillis() + waitInterval; if (DEBUG) Log.d(tag, "longTimeLeft=" + longTimeLeft); setChronometer(longTimeLeft); if (DEBUG) Log.d(tag, "Finished"); } private void resetTimeLeft(){ String tag = TAG + "resetTimeLeft"; if (DEBUG) Log.d(tag, "Starting"); longTimeLeft = 0; setChronometer(longLoopInterval); if (DEBUG) Log.d(tag, "Finished"); } private void playSound(){ String tag = TAG + "playSound"; if (DEBUG) Log.d(tag, "Starting"); /* Original code, did not work on Nexus 7 with CM 12.1-20151117 (Android 5.1.1) * / Uri uri = Uri.parse("android.resource://" + getPackageName() + "/raw/chime_sound_7143"); //Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), ringtoneUri); Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri); ringtone.play(); /**/ try { mediaPlayer.start(); } catch (Exception e) { sharedPreferences.edit().putBoolean(keyStartStop, false).apply(); String message = "Failed to play a notification sound."; message += " Please screenshot this error and send it to me@hyperling.com:\n\n"; message += e.toString(); new AlertDialog.Builder(MainActivity.this) .setTitle("Error") .setMessage(message) .setPositiveButton("Exit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); finish(); } }) .setCancelable(false) .show() ; } if (DEBUG) Log.d(tag, "Finished"); } private void flipInputTexts() { String tag = TAG + "flipInputTexts"; if (DEBUG) Log.d(tag, "Starting"); etHours.setEnabled(!start); etMinutes.setEnabled(!start); etSeconds.setEnabled(!start); if (DEBUG) Log.d(tag, "Finished"); } private void resetAll() { String tag = TAG + "resetAll"; if (DEBUG) Log.d(tag, "Starting"); if (DEBUG) Log.d(tag, "Resetting screen values..."); setChronometer(0); setEditTexts(""); setLoopInterval(); if (DEBUG) Log.d(tag, "Values reset"); if (DEBUG) Log.d(tag, "Resetting SharedPreferences..."); start = false; SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean(keyStartStop, start); editor.putBoolean(keyAppOpen, start); editor.putBoolean(keyServiceRunning, start); int count = 0; while (!editor.commit() && count < 50) { Log.i(tag, "Failed! Reattempting commit... count=" + count); count++; }; if (DEBUG) Log.d(tag, "SharedPreferences reset"); if (DEBUG) Log.d(tag, "Finished"); } public void syncVolume() { String tag = TAG + "syncVolume"; if (DEBUG) Log.d(tag, "Starting"); int currVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (currVolume != seekBar.getProgress()) { seekBar.setProgress(currVolume); } String volume = getString(R.string.tvSeekBar) + " " + seekBar.getProgress() + "/" + seekBar.getMax(); tvSeekBar.setText(volume); if (DEBUG) Log.d(tag, "Finished"); } @Override protected void onPause() { String tag = TAG + "onPause"; if (DEBUG) Log.d(tag, "Starting"); stringLoopInterval = etHours.getText().toString() + ":" + etMinutes.getText().toString() + ":" + etSeconds.getText().toString(); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString(keyChronometerTime, chronometer.getText().toString()); editor.putString(keyLoopInterval, stringLoopInterval); editor.putBoolean(keyAppOpen, false); editor.apply(); if (DEBUG) Log.d(tag, "Done with local code, calling super."); super.onPause(); if (DEBUG) Log.d(tag, "Finished"); } @Override protected void onResume() { super.onResume(); String tag = TAG + "onResume"; if (DEBUG) Log.d(tag, "Starting"); sharedPreferences.edit().putBoolean(keyServiceRunning, false).apply(); recoverScreen(); syncVolume(); if (DEBUG) Log.d(tag, "Finished"); } @Override protected void onDestroy() { String tag = TAG + "onDestroy"; if (DEBUG) Log.d(tag, "Starting"); resetAll(); if (DEBUG) Log.d(tag, "Done with local code, calling super.."); super.onDestroy(); if (DEBUG) Log.d(tag, "Finished"); } // TODO: This is somehow always 1 action behind the actual volume when pressing volume buttons. // Like when doing down+up+down, the bar does nothing+down+up. @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { boolean bool = super.onKeyDown(keyCode, event); syncVolume(); return bool; } return super.onKeyDown(keyCode, event); }}