I'm working on making my first Android App, and I cannot get the text to speech to work.
The gist of the app: user selects the time they want to set an alarm (using a TimePicker), then the app calculates the amount of time left, continuously updating the amount of time left.
I want the use the text to speech engine to verbally update the user at 5 minute intervals on how much time they have left.
Everything compiles and runs without error, but the text to speech just doesn't do anything. I've looked for days to find out how to properly initialize the TextToSpeech class in java, but everything I find seems to initialize it differently on a case by case basis.
After trying multiple different implementations, all with the same result, I decided to just make a separate inner class to make editing and trying new things easier, and I can just make a Voice class and call methods on it.
I've only been coding in Java for a few months, and this is my first Android App, so I am having a lot of trouble sorting through this on my own.
Below is the separate (inner/nested) class that I am using to initialize the TextToSpeech class:
private class Voice implements TextToSpeech.OnInitListener {
Context context = getApplicationContext();
TextToSpeech tts = new TextToSpeech(context, this);
public void onInit(int initStatus) {
if (initStatus == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.US);
}
}
private void say(String announcement) {
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null);
}
}
Here is the rest of the code in the activity ( I use a separate activity to display the countdown)
import android.content.Context;
import android.speech.tts.TextToSpeech;
import android.support.v7.app.AppCompatActivity;
import android.os.*;
import android.widget.*;
import java.util.*;
import java.lang.*;
public class TimerActivity extends AppCompatActivity{
private int alarmHour = MainActivity.alarmHour;
private int alarmMinute = MainActivity.alarmMinute;
private Calendar alarmTime;
private Calendar currentTime;
private TextView timerText;
private TextView lateText;
private int hoursLeft;
private int minutesLeft;
private long difference;
private long calculatedMinutes;
private boolean late = false;
private String lateMessageString = "LATE!";
private String timeRemainingString = "remaining";
private Handler handler = new Handler();
private TextToSpeech tts;
private double pitch = 1;
private double speed = 1;
private int MY_DATA_CHECK_CODE = 0;
private Voice voice;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer);
voice = new Voice();
voice.say("This is some bullshit.");
initializeTimer();
}
private void initializeTimer () {
voice.say("Yes, hello. Is it me you're looking for?");
timerText = (TextView) findViewById(R.id.timerText);
lateText = (TextView) findViewById(R.id.lateText);
setAlarm();
calculateTimeLeft();
String timerString = getTimerString();
timerText.setText(timerString);
if (late) {
lateText.setText(lateMessageString);
}
else {
lateText.setText(timeRemainingString);
}
Timer timer = new Timer();
timer.schedule(new UpdateTimer(),100,60000);
}
private class UpdateTimer extends TimerTask {
public void run() {
handler.post(new Runnable() {
public void run() {
calculateTimeLeft();
String timerString = getTimerString();
timerText.setText(timerString);
if (late) {
lateText.setText(lateMessageString);
}
else {
lateText.setText(timeRemainingString);
}
}
});
}
}
private void setAlarm() {
alarmTime = Calendar.getInstance();
currentTime = Calendar.getInstance();
alarmTime.setTimeInMillis(System.currentTimeMillis());
alarmTime.set(Calendar.HOUR_OF_DAY, alarmHour);
alarmTime.set(Calendar.MINUTE, alarmMinute);
// If alarm is set for the past, then set the alarm for the next day at the specified time
if (alarmTime.getTimeInMillis() - System.currentTimeMillis() < 0) {
alarmTime.set(Calendar.DAY_OF_YEAR, alarmTime.get(Calendar.DAY_OF_YEAR) + 1);
}
}
private void calculateTimeLeft() {
currentTime.setTimeInMillis(System.currentTimeMillis());
difference = alarmTime.getTimeInMillis() - System.currentTimeMillis();
long seconds = difference/1000; // convert to seconds
long calculatedHours = seconds / 3600; // Find the number of hours until alarm
calculatedMinutes = (seconds % 3600) / 60; // Use mod to remove the number of hours, leaving only seconds since the last hour, then divide by 60 to get minutes
hoursLeft = (int)Math.abs(calculatedHours); // Get the absolute value of the time left for the string
minutesLeft = (int)Math.abs(calculatedMinutes); // Absolute value of the minutes for string use
}
private String getTimerString() {
// Format the string showing the time remaining
String timeLeftString;
if (hoursLeft == 0) {
timeLeftString = "";
}
else if (hoursLeft == 1) {
timeLeftString = hoursLeft + "hr ";
}
else {
timeLeftString = hoursLeft +"hrs ";
}
if (hoursLeft == 0) {
timeLeftString += minutesLeft;
}
/*
else if (minutesLeft < 10 && calculatedMinutes > 0) {
timeLeftString += "0" + minutesLeft;
}
*/
else {
timeLeftString += minutesLeft;
}
if (calculatedMinutes >= 0 && hoursLeft > 0) {
timeLeftString += " mins";
}
else if (calculatedMinutes > 0) {
timeLeftString += " mins";
}
else if (difference <= 0) {
late = true;
}
return timeLeftString;
}
private class Voice implements TextToSpeech.OnInitListener {
Context context = getApplicationContext();
TextToSpeech tts = new TextToSpeech(context, this);
public void onInit(int initStatus) {
if (initStatus == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.US);
}
}
private void say(String announcement) {
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null);
}
}
}
I think you can't hear any voices that you want.
Because TextToSpeech class can't say immediately after you invoke constructor.
TextToSpeech class instance need to connect to system TTS service when it is initializing.
And if the instance is success to connect to service, onInit of TextToSpeech.OnInitListener will be activated!
So, you can hear the voice after onInit function is done. Try to move your voice.say functions at onInit function.
private class Voice implements TextToSpeech.OnInitListener {
Context context = getApplicationContext();
TextToSpeech tts = new TextToSpeech(context, this);
public void onInit(int initStatus) {
if (initStatus == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.US);
// try it!
voice.say("Can you hear this sentence?");
// If you want to another "say", check this log.
// Your voice will say after you see this log at logcat.
Log.i("TAG", "TextToSpeech instance initialization is finished.");
}
}
private void say(String announcement) {
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null);
}
}
Related
I have a simple stopwatch code piece. Thread is running in custom class, it connects to the main activity via Interface
public class MainActivity extends AppCompatActivity implements MainActivityInteractionInterface{
public static boolean isRunning = false;
Stopwatch stopWatch;
private TextView textViewMilliSeconds;
private TextView textViewSeconds;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textViewMilliSeconds = findViewById(R.id.textViewStopwatchMilliseconds);
textViewSeconds = findViewById(R.id.textViewStopwatchSeconds);
stopWatch = new Stopwatch(this, getApplicationContext());
stopWatch.runThread();
}
#Override
public void updateUI() {
String time = String.format(Locale.getDefault(), "%03d", stopWatch.getMilliseconds());
textViewMilliSeconds.setText(time);
String timeSeconds = String.format(Locale.getDefault(), "%02d", stopWatch.getSeconds());
textViewSeconds.setText(timeSeconds);
}
public void startTimer(View view) {
isRunning = !isRunning;
}
public class Stopwatch {
private int milliseconds = 0;
private int seconds = 0;
public int getMilliseconds() {
return milliseconds;
}
public int getSeconds() {
return seconds;
}
private MainActivityInteractionInterface interactionInterface;
private Context applicationContext;
public Stopwatch(MainActivityInteractionInterface interactionInterface, Context applicationContext){
this.interactionInterface = interactionInterface;
this.applicationContext = applicationContext;
}
public void runThread(){
final Handler handler = new Handler();
handler.post(new Runnable(){
#Override
public void run(){
if(isRunning) {
milliseconds++;
if (milliseconds == 1000) {
milliseconds = 0;
seconds++;
if(seconds == 60){
seconds = 0;
}
}
}
interactionInterface.updateUI();
handler.postDelayed(this, 1);
}
});
}
handler should update every 1 millisec, when there is 1000 milliseconds, 1 second passes by
If I set handler.postDelayed delay anything below 15 reaching 1000 milliseconds would take exactly 18 seconds, why?
I don't know why it would take up to 18seconds, but I can tell you this: Android refresh the UI every 16msec (to have a rate of 60fps), so setting the handler to updateUI in a lesser time would make no sense and maybe also interfier with it.
In my humble opinion, make it to update in 20msec and change the counter values according, like this:
handler.post(new Runnable(){
#Override
public void run(){
if(isRunning) {
milliseconds++;
if (milliseconds == 50) {
milliseconds = 0;
seconds++;
if(seconds == 60){
seconds = 0;
}
}
}
interactionInterface.updateUI();
handler.postDelayed(this, 20);
}
});
Look at the second argument of handler.postDelayed(this, 1);
Change it according to the way you increment your milliseconds.
I am making an Guitar app that have 3 activities and each activity include a audio function, reason why i am using SoundPool, and I have 66 samples.
My problem is that I have to load them in each and every activity, so my question is, is there any way that I upload those 66 samples right after my app starts, and keep them loaded in every activity ?
You can use a simple utility class for SoundPool. You can use a public static method so that it can be instantiated once and be accessed from any activity. Here a class that can be used for your case:
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.util.Log;
public class SoundPoolManager {
private static final String TAG = SoundPoolManager.class.getSimpleName();
private static SoundPool soundPool;
private static int[] sm;
private Context context;
private static float mVolume;
private static SoundPoolManager instance;
private static final int SOUND_TOTAL = 1;
private SoundPoolManager(Context context) {
this.context = context;
initSound();
// add sound here
// here the sample audio file which can be use with your audio file
int soundRawId = R.raw.watch_tick;
//you need to change SOUND_TOTAL for the size of the audio samples.
sm[sm.length - 1] = soundPool.load(context, soundRawId, 1);
}
public static void instantiate(Context context) {
if(instance == null) instance = new SoundPoolManager(context);
}
private void initSound() {
sm = new int[SOUND_TOTAL];
int maxStreams = 1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
soundPool = new SoundPool.Builder()
.setMaxStreams(maxStreams)
.build();
} else {
soundPool = new SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0);
}
mVolume = setupVolume(context);
}
private float setupVolume(Context context) {
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if(am == null) {
Log.e(TAG, "Can't access AudioManager!");
return 0;
}
float actualVolume = (float) am.getStreamVolume(AudioManager.STREAM_ALARM);
float maxVolume = (float) am.getStreamMaxVolume(AudioManager.STREAM_ALARM);
return actualVolume / maxVolume;
}
public static void playSound(int index) {
if(sm == null) {
Log.e(TAG, "sm is null, this should not happened!");
return;
}
if(soundPool == null) {
Log.e(TAG, "SoundPool is null, this should not happened!");
return;
}
if(sm.length <= index) {
Log.e(TAG, "No sound with index = " + index);
return;
}
if(mVolume > 0) {
soundPool.play(sm[index], mVolume, mVolume, 1, 0, 1f);
}
}
public static void cleanUp() {
sm = null;
if(soundPool != null) {
soundPool.release();
soundPool = null;
}
}
}
Then you can use the class with the following:
// need to call this for the first time
SoundPoolManager.instantiate(context);
// play the sound based on the index
SoundPoolManager.playSound(index);
// clear up the SoundPool when you don't need it anymore.
SoundPoolManager.cleanUp();
Hi guys ive just implemented the open source project Auto Update apk.. Because my app is not on play store and it was getting very silly waiting for people to download new updates.. ive had no problems getting it to work it was pretty straight forward to implement.. My problem is the amount of data allowance they give u on a free account... ive used all my quote for the month in less than aweek.. what want to know is how i can change the code so it checks aweb address of my choosing instead of theres.. ive read that it would involve a .txt file that my app would read with new version number inside..
any ideas of what to change in the auto update apk class to achive this? also what would the txt file need to contain. thanks guys n girls
this is the main code for the auto updateapk project
public class AutoUpdateApk extends Observable {
// this class is supposed to be instantiated in any of your activities or,
// better yet, in Application subclass. Something along the lines of:
//
// private AutoUpdateApk aua; <-- you need to add this line of code
//
// public void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// setContentView(R.layout.main);
//
// aua = new AutoUpdateApk(getApplicationContext()); <-- and add this line too
//
public AutoUpdateApk(Context ctx) {
setupVariables(ctx);
}
// set icon for notification popup (default = application icon)
//
public static void setIcon( int icon ) {
appIcon = icon;
}
// set name to display in notification popup (default = application label)
//
public static void setName( String name ) {
appName = name;
}
// set update interval (in milliseconds)
//
// there are nice constants in this file: MINUTES, HOURS, DAYS
// you may use them to specify update interval like: 5 * DAYS
//
// please, don't specify update interval below 1 hour, this might
// be considered annoying behaviour and result in service suspension
//
public void setUpdateInterval(long interval) {
if( interval > 60 * MINUTES ) {
UPDATE_INTERVAL = interval;
} else {
Log_e(TAG, "update interval is too short (less than 1 hour)");
}
}
// software updates will use WiFi/Ethernet only (default mode)
//
public static void disableMobileUpdates() {
mobile_updates = false;
}
// software updates will use any internet connection, including mobile
// might be a good idea to have 'unlimited' plan on your 3.75G connection
//
public static void enableMobileUpdates() {
mobile_updates = true;
}
// call this if you want to perform update on demand
// (checking for updates more often than once an hour is not recommended
// and polling server every few minutes might be a reason for suspension)
//
public void checkUpdatesManually() {
checkUpdates(true); // force update check
}
public static final String AUTOUPDATE_CHECKING = "autoupdate_checking";
public static final String AUTOUPDATE_NO_UPDATE = "autoupdate_no_update";
public static final String AUTOUPDATE_GOT_UPDATE = "autoupdate_got_update";
public static final String AUTOUPDATE_HAVE_UPDATE = "autoupdate_have_update";
public void clearSchedule() {
schedule.clear();
}
public void addSchedule(int start, int end) {
schedule.add(new ScheduleEntry(start,end));
}
//
// ---------- everything below this line is private and does not belong to the public API ----------
//
protected final static String TAG = "AutoUpdateApk";
private final static String ANDROID_PACKAGE = "application/vnd.android.package-archive";
// private final static String API_URL = "http://auto-update-apk.appspot.com/check";
private final static String API_URL = "http://www.auto-update-apk.com/check";
protected static Context context = null;
protected static SharedPreferences preferences;
private final static String LAST_UPDATE_KEY = "last_update";
private static long last_update = 0;
private static int appIcon = R.mipmap.ic_launcher;
private static int versionCode = 0; // as low as it gets
private static String packageName;
private static String appName;
private static int device_id;
public static final long MINUTES = 60 * 1000;
public static final long HOURS = 60 * MINUTES;
public static final long DAYS = 24 * HOURS;
// 3-4 hours in dev.mode, 1-2 days for stable releases
private static long UPDATE_INTERVAL = 48 * DAYS; // how often to check
private static boolean mobile_updates = false; // download updates over wifi only
private final static Handler updateHandler = new Handler();
protected final static String UPDATE_FILE = "update_file";
protected final static String SILENT_FAILED = "silent_failed";
private final static String MD5_TIME = "md5_time";
private final static String MD5_KEY = "md5";
private static int NOTIFICATION_ID = 0xBEEF;
private static long WAKEUP_INTERVAL = 15 * MINUTES;
private class ScheduleEntry {
public int start;
public int end;
public ScheduleEntry(int start, int end) {
this.start = start;
this.end = end;
}
}
private static ArrayList<ScheduleEntry> schedule = new
ArrayList<ScheduleEntry>();
private Runnable periodicUpdate = new Runnable() {
#Override
public void run() {
checkUpdates(false);
updateHandler.removeCallbacks(periodicUpdate); // remove whatever others may have posted
updateHandler.postDelayed(this, WAKEUP_INTERVAL);
}
};
private BroadcastReceiver connectivity_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
NetworkInfo currentNetworkInfo = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
// do application-specific task(s) based on the current network state, such
// as enabling queuing of HTTP requests when currentNetworkInfo is connected etc.
boolean not_mobile = currentNetworkInfo.getTypeName().equalsIgnoreCase("MOBILE") ? false : true;
if( currentNetworkInfo.isConnected() && (mobile_updates || not_mobile) ) {
checkUpdates(false);
updateHandler.postDelayed(periodicUpdate, UPDATE_INTERVAL);
} else {
updateHandler.removeCallbacks(periodicUpdate); // no network anyway
}
}
};
private void setupVariables(Context ctx) {
context = ctx;
packageName = context.getPackageName();
preferences = context.getSharedPreferences( packageName + "_" + TAG, Context.MODE_PRIVATE);
device_id = crc32(Secure.getString( context.getContentResolver(), Secure.ANDROID_ID));
last_update = preferences.getLong("last_update", 0);
NOTIFICATION_ID += crc32(packageName);
// schedule.add(new ScheduleEntry(0,24));
ApplicationInfo appinfo = context.getApplicationInfo();
if( appinfo.icon != 0 ) {
appIcon = appinfo.icon;
} else {
Log_w(TAG, "unable to find application icon");
}
if( appinfo.labelRes != 0 ) {
appName = context.getString(appinfo.labelRes);
} else {
Log_w(TAG, "unable to find application label");
}
if( new File(appinfo.sourceDir).lastModified() > preferences.getLong(MD5_TIME, 0) ) {
preferences.edit().putString( MD5_KEY, MD5Hex(appinfo.sourceDir)).commit();
preferences.edit().putLong( MD5_TIME, System.currentTimeMillis()).commit();
String update_file = preferences.getString(UPDATE_FILE, "");
if( update_file.length() > 0 ) {
if( new File( context.getFilesDir().getAbsolutePath() + "/" + update_file ).delete() ) {
preferences.edit().remove(UPDATE_FILE).remove(SILENT_FAILED).commit();
}
}
}
raise_notification();
if( haveInternetPermissions() ) {
context.registerReceiver( connectivity_receiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
private boolean checkSchedule() {
if( schedule.size() == 0 ) return true; // empty schedule always fits
int now = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
for( ScheduleEntry e : schedule ) {
if( now >= e.start && now < e.end ) return true;
}
return false;
}
// required in order to prevent issues in earlier Android version.
private static void disableConnectionReuseIfNecessary() {
// see HttpURLConnection API doc
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
private static ArrayList<ScheduleEntry> schedule = new ArrayList<ScheduleEntry>();
private Runnable periodicUpdate = new Runnable() {
#Override
public void run() {
checkUpdates(false);
updateHandler.removeCallbacks(periodicUpdate); // remove whatever others may have posted
updateHandler.postDelayed(this, WAKEUP_INTERVAL);
}
};
private BroadcastReceiver connectivity_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
NetworkInfo currentNetworkInfo = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
// do application-specific task(s) based on the current network state, such
// as enabling queuing of HTTP requests when currentNetworkInfo is connected etc.
boolean not_mobile = currentNetworkInfo.getTypeName().equalsIgnoreCase("MOBILE") ? false : true;
if( currentNetworkInfo.isConnected() && (mobile_updates || not_mobile) ) {
checkUpdates(false);
updateHandler.postDelayed(periodicUpdate, UPDATE_INTERVAL);
} else {
updateHandler.removeCallbacks(periodicUpdate); // no network anyway
}
}
};
private void setupVariables(Context ctx) {
context = ctx;
packageName = context.getPackageName();
preferences = context.getSharedPreferences( packageName + "_" + TAG, Context.MODE_PRIVATE);
device_id = crc32(Secure.getString( context.getContentResolver(), Secure.ANDROID_ID));
last_update = preferences.getLong("last_update", 0);
NOTIFICATION_ID += crc32(packageName);
// schedule.add(new ScheduleEntry(0,24));
ApplicationInfo appinfo = context.getApplicationInfo();
if( appinfo.icon != 0 ) {
appIcon = appinfo.icon;
} else {
Log_w(TAG, "unable to find application icon");
}
if( appinfo.labelRes != 0 ) {
appName = context.getString(appinfo.labelRes);
} else {
Log_w(TAG, "unable to find application label");
}
if( new File(appinfo.sourceDir).lastModified() > preferences.getLong(MD5_TIME, 0) ) {
preferences.edit().putString( MD5_KEY, MD5Hex(appinfo.sourceDir)).commit();
preferences.edit().putLong( MD5_TIME, System.currentTimeMillis()).commit();
String update_file = preferences.getString(UPDATE_FILE, "");
if( update_file.length() > 0 ) {
if( new File( context.getFilesDir().getAbsolutePath() + "/" + update_file ).delete() ) {
preferences.edit().remove(UPDATE_FILE).remove(SILENT_FAILED).commit();
}
}
}
raise_notification();
if( haveInternetPermissions() ) {
context.registerReceiver( connectivity_receiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
private boolean checkSchedule() {
if( schedule.size() == 0 ) return true; // empty schedule always fits
int now = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
for( ScheduleEntry e : schedule ) {
if( now >= e.start && now < e.end ) return true;
}
return false;
}
// required in order to prevent issues in earlier Android version.
private static void disableConnectionReuseIfNecessary() {
// see HttpURLConnection API doc
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
I was trying to build some sort of sequential countdown. Meaning, that I build up a queue of "exercises", each one containing a specific duration, which is the countdown time. In a custom Countdown class, I pop these exercises off the queue and use the duration as countdown.
I want these countdowns to run one after another. For this I built a Countdown class, based on the code basis of the abstract class CountDownTimer.
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Locale;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.widget.Button;
import android.widget.TextView;
public class ExerciseMeCountDownTimer {
private static final int MSG_COUNTDOWN = 100;
private static final int MSG_FINISH = 99;
private ArrayDeque<Exercise> eq;
private long mMillisInFuture;
private int mCountdownInterval;
private String name;
private long mStopTimeInFuture;
CountdownHandler cHandler;
public ExerciseMeCountDownTimer(ArrayList<Exercise> elist,
Button startStopButton, TextView countdownText,
CountdownHandler cHandler) {
this.cHandler = cHandler;
eq = new ArrayDeque<Exercise>(elist);
this.start();
}
public final void cancel() {
mHandler.removeMessages(MSG);
}
private synchronized final ExerciseMeCountDownTimer start() {
if (!eq.isEmpty()) {
Exercise e = eq.pop();
this.mMillisInFuture = Long.parseLong(e.getDuration());
this.mCountdownInterval = 30;
this.name = e.getName();
} else {
onFinish();
return this;
}
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
public void onTick(long millisUntilFinished) {
Message msg = cHandler.obtainMessage(MSG_COUNTDOWN);
Bundle data = new Bundle();
String text = String.format(Locale.GERMANY, "%02d:%02d:%03d",
millisUntilFinished / 100000, millisUntilFinished / 1000,
millisUntilFinished % 1000);
data.putString("countdown", text);
msg.setData(data);
cHandler.sendMessage(msg);
}
public void onFinish() {
if (!eq.isEmpty()) {
this.start();
}
Message msg = cHandler.obtainMessage(MSG_FINISH);
Bundle data = new Bundle();
String text = String.format(Locale.GERMANY, "00:00:000");
data.putString("finish", text);
msg.setData(data);
cHandler.sendMessage(msg);
}
private static final int MSG = 1;
// handles counting down
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
final long millisLeft = mStopTimeInFuture
- SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// take into account user's onTick taking time to
// execute
long delay = lastTickStart + mCountdownInterval
- SystemClock.elapsedRealtime();
// special case: user's onTick took more than interval
// to
// complete, skip to next interval
while (delay < 0)
delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
};
#Override
public String toString() {
return this.name;
}
}
The important part is the sendMessage part, where I send the time left on the countdown to a handler of my MainActivity, which then, should update a textview.
import android.os.Handler;
import android.os.Message;
class CountdownHandler extends Handler {
private static final int MSG_COUNTDOWN = 100;
private static final int MSG_FINISH = 99;
private MainActivity mActivity;
CountdownHandler(MainActivity activity) {
this.mActivity = activity;
}
#Override
public void handleMessage(Message msg) {
if (msg.what == MSG_COUNTDOWN) {
String text = msg.getData().getString("countdown");
this.mActivity.sayLog(text);
}
if (msg.what == MSG_FINISH) {
String text = msg.getData().getString("finish");
this.mActivity.sayLog(text);
}
}
And finally updates the textView in MainActivty
public void sayLog(String text) {
countdown.setText(text);
}
ExerciseMeCountDownTimer is called by
new ExerciseMeCountDownTimer(elist, cHandler);
on some onClick().
The problem is, that sometimes (actually most of the time) the textView is not updated properly. It stops updating on random times like 00:05:211 etc.
Would anyone mind telling me why this is keeps happening? Maybe also adding a solution or at least some literature (maybe pointing out some sections) which I should read to understand the problem? I am also upen for alternative approaches, as I am new to this "handler", "threads" thing in android.
EDIT
the textview was updating, but the textview was clickable. Whenever I clicked on the textview it stopped updating!
as the accepted answer shows, I decided to use the direkt approach of updating the appropriate textview inside the onTick() method.
Using Handler and things in this situation is making it overly complicated.
CountDownTimers onTick() and onFinish() both run on the UI Thread so updating TextViews and other Views from either method can be done easily just by passing a reference of the View to the constructor of the class, as you are already doing. Then you simply update it in the method needed.
// could create a member variable for the TextView with your other member variables
...
mTV;
then in your constructor assign it
// removed reference to Handler--you already have reference to TextView here
public ExerciseMeCountDownTimer(ArrayList elist,
Button startStopButton, TextView countdownText) {
mTV = countdownText;
then update in whichever method is needed
public void onTick(long millisUntilFinished) {
String text = String.format(Locale.GERMANY, "%02d:%02d:%03d",
millisUntilFinished / 100000, millisUntilFinished / 1000,
millisUntilFinished % 1000);
mTV.setText(text); // set the text here
}
public void onFinish() {
if (!eq.isEmpty()) {
this.start();
}
I have a runnable timer that update a textview every second, when the activity is onStop (or called into the background) the timer continues to run. The issue i am having is that when i re-launch the activity it starts the same timer again, so the numbers are going up twice as fast as they should. I have it coded so that it will kill both timers before it restarts them but i believe that when the activity is started again the timers are not being killed. Here is an example of my code :
t.cancel();
cd.cancel();
t = new Timer();
t.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
That is only a small part but it should kill the timer (t.cancel();) then start a new one, this only happens when the activity is stopped and then restarted. please help this issue is driving me absolutely insane.
=========================================================
For the brave souls willing to read alot, here is my entire activity that i am having the issue with:
public class PayTracker extends Activity {
private static double Reserve;
private static int Reserve1;
public static double money;
public static double counter;
private static int go;
private static int countdown;
public static int convert;
public static double HW;
public static double OTW;
public static double HPD;
public static double DPPS;
public Timer t = new Timer();
public Timer cd = new Timer();
public static String mcountdown = "Time till overtime";
public static String mmoney = "total cash";
public static String mcounter = "ticks";
public static String mReserve = "building total";
public static String mReserve1 = "building total 2";
public static String mHW;
public static String mOTW;
public static String mHPD;
public static String mDPPS;
public static String mgo;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pay_tracker);
getActionBar().setDisplayHomeAsUpEnabled(true);
// Receive messages from options page
double pHW, pOTW, pHPD;
Intent intent = getIntent();
pHW = intent.getDoubleExtra(Options.MESSAGE_HW, 0);
pOTW = intent.getDoubleExtra(Options.MESSAGE_OTW, 0);
pHPD = intent.getDoubleExtra(Options.MESSAGE_HPD, 0);
if(pHW != 0){
HW = pHW;
OTW = pOTW;
HPD = pHPD;
}
// Color buttons
Button buttonc = (Button) findViewById(R.id.clockin);
buttonc.getBackground().setColorFilter(0xFF00FF00, PorterDuff.Mode.MULTIPLY);
Button buttond = (Button) findViewById(R.id.clockout);
buttond.getBackground().setColorFilter(0xFFFF0000, PorterDuff.Mode.MULTIPLY);
// go = 0;
// Calculate pay per second
final double PPS = (HW/3600);
DPPS = (PPS/50);
final double OTPPS = (OTW/3600);
final double DOTPPS = (OTPPS/50);
final double HPDPS = (HPD*3600);
final double DHPDPS = (HPDPS*50);
// Display
final TextView t1 = (TextView) findViewById(R.id.yourpay);
t1.setTextColor(Color.parseColor("#008000"));
final TextView t2 = (TextView) this.findViewById(R.id.payper);
final String result2 = String.format("%.8f", OTPPS);
final String result = String.format("%.8f", PPS);
// if(go != 1){
// go = 1;
// if(go == 1){
t.cancel();
cd.cancel();
// go = 0;
// }
// if(go == 0){
// go = 1;
t = new Timer();
t.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
if(DHPDPS==0){
money = (DPPS+Reserve);
Reserve = (money);
String end = String.format("%1f", money);
t1.setText("$" + end);
}else if(counter > DHPDPS && DOTPPS != 0 && DHPDPS != 0){
money = (DOTPPS+Reserve);
Reserve = (money);
String end = String.format("%1f", money);
t1.setText("$" + end);
} else{
money = (DPPS+Reserve);
Reserve = (money);
String end = String.format("%1f", money);
t1.setText("$" + end);
}
counter++;
//if(counter == 3000)
// t.cancel();
// Display pay per second
if(counter <= DHPDPS || DHPDPS == 0){
t2.setText("Your pay per second is: $"+result);
}else{
t2.setText("Your pay per second is: $"+result2);
}
}
});
}
}, 20, 20);
// Make countdown to overtime display
final Intent intent1 = new Intent(this, PayTracker.class);
// Create the notification
final Notification notification = new Notification(R.drawable.ic_launcher, "Click here to check your pay!", System.currentTimeMillis());
// Create an Intent for the notification to launch
// Create a PendingIntent for the associated Intent
final PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent1, 0);
cd = new Timer();
final TextView count = (TextView) findViewById(R.id.countdown);
convert = (int)HPDPS;
cd.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run(){
countdown = (convert - Reserve1);
int hours = (countdown/3600);
if(OTPPS != 0 && HPDPS != 0){
count.setText("Seconds Remaining to Overtime: " + countdown + "\nAbout " + hours + " Hours");
Reserve1++;
}
// Set the notification's details
final String end = String.format("%.6f", money);
notification.setLatestEventInfo(getApplicationContext(), "Your Current Pay:", "$"+end, pendingIntent);
// Submit the notification to the system
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(0, notification);
}
});
}
}, 1000, 1000);
// }
// }
final Button b = (Button) findViewById(R.id.clockout);
b.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if(go == 1)
go = 0;
if (t != null){
t.cancel();
cd.cancel();
}
}
});
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore value of members from saved state
countdown = savedInstanceState.getInt(mcountdown);
Reserve = savedInstanceState.getInt(mReserve);
money = savedInstanceState.getInt(mmoney);
counter = savedInstanceState.getInt(mcounter);
Reserve1 = savedInstanceState.getInt(mReserve1);
HW = savedInstanceState.getInt(mHW);
OTW = savedInstanceState.getInt(mOTW);
HPD = savedInstanceState.getInt(mHPD);
DPPS = savedInstanceState.getInt(mDPPS);
go = savedInstanceState.getInt(mgo);
}
#Override
public void onStart(){
super.onStart();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_pay_tracker, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
public void sendMessage(View view) {
// Calculate pay per second
final double PPS = (HW/3600);
DPPS = (PPS/50);
final double OTPPS = (OTW/3600);
final double DOTPPS = (OTPPS/50);
final double HPDPS = (HPD*3600);
final double DHPDPS = (HPDPS*50);
// Display
final TextView t1 = (TextView) findViewById(R.id.yourpay);
t1.setTextColor(Color.parseColor("#008000"));
final TextView t2 = (TextView) this.findViewById(R.id.payper);
final String result2 = String.format("%.8f", OTPPS);
final String result = String.format("%.8f", PPS);
//if(go != 1){
// go = 1;
t.cancel();
cd.cancel();
t = new Timer();
t.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
if(DHPDPS==0){
money = (DPPS+Reserve);
Reserve = (money);
String end = String.format("%1f", money);
t1.setText("$" + end);
}else if(counter > DHPDPS && DOTPPS != 0 && DHPDPS != 0){
money = (DOTPPS+Reserve);
Reserve = (money);
String end = String.format("%1f", money);
t1.setText("$" + end);
} else{
money = (DPPS+Reserve);
Reserve = (money);
String end = String.format("%1f", money);
t1.setText("$" + end);
}
counter++;
if(counter == 3000)
t.cancel();
// Display pay per second
if(counter <= DHPDPS || DHPDPS == 0){
t2.setText("Your pay per second is: $"+result);
}else{
t2.setText("Your pay per second is: $"+result2);
}
}
});
}
}, 20, 20);
// Make countdown to overtime display
final Intent intent1 = new Intent(this, PayTracker.class);
// Create the notification
final Notification notification = new Notification(R.drawable.ic_launcher, "Click here to check your pay!", System.currentTimeMillis());
// Create an Intent for the notification to launch
// Create a PendingIntent for the associated Intent
final PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent1, 0);
cd = new Timer();
final TextView count = (TextView) findViewById(R.id.countdown);
convert = (int)HPDPS;
cd.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run(){
countdown = (convert - Reserve1);
int hours = (countdown/3600);
if(OTPPS != 0 && HPDPS != 0){
count.setText("Seconds Remaining to Overtime: " + countdown + "\nAbout " + hours + " Hours");
Reserve1++;
}
// Set the notification's details
final String end = String.format("%.6f", money);
notification.setLatestEventInfo(getApplicationContext(), "Your Current Pay:", "$"+end, pendingIntent);
// Submit the notification to the system
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(0, notification);
}
});
}
}, 1000, 1000);
//}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(mcountdown, countdown);
savedInstanceState.putDouble(mReserve, Reserve);
savedInstanceState.putDouble(mmoney, money);
savedInstanceState.putDouble(mcounter, counter);
savedInstanceState.putDouble(mReserve1, Reserve1);
savedInstanceState.putDouble(mHW, HW);
savedInstanceState.putDouble(mOTW, OTW);
savedInstanceState.putDouble(mHPD, HPD);
savedInstanceState.putDouble(mDPPS, DPPS);
savedInstanceState.putInt(mgo, go);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
#Override
public void onDestroy() {
super.onDestroy();
if(t != null)
t.cancel();
if(cd != null)
cd.cancel();
}
}
This is one way to get around that.
static Timer mTimer = null;
onCreate() {
if (mTimer == null)
mTimer = new Timer();
} else {
// You shouldn't have to do nothing because your timer should be running
}
}
Note that there are several issues in general here. The static is basically saying just to create one instance of that object. As a side effect it also tries to reclaim the same memory address. Either way, once your app is in the background it can be cleaned up by the system at any time so your not guaranteed to get your Timer back. There are a bunch of other ways to get around that, but that is out of scope for this question.