I've just finished implementing my app's weather icons, now I'm trying to play weather sounds on the app based on the icons the app is displaying. After doing long hours of thorough research, I found out to my shock that no one has ever asked similar questions on any platform.
My knowledge of java dictionary is quite narrow and I find it hard to understand documentation even after taking my time to read and study their samples. i.e I found a doc I believe would correspond with what I am trying to achieve here https://docs.oracle.com/javase/8/docs/api/java/util/Map.html
It explains that
V put(K key,
V value)
Associates the specified value with the specified key in this map. In that case, I thought of using K as Icon and V as my sound but I didn't know how to apply it so I looked for examples and saw this https://study.com/academy/lesson/the-java-dictionary-class-definition-example.html The examples it provided still wasn't enough to help me through and I know I will mess it up if I try to work it out myself, so I decided to come up with this here to see if anyone can help, I'll really appreciate it.
Following the API instructions, I'm using https://openweathermap.org/weather-conditions.
There are 9 main weather conditions.
These are my goals in trying to play the sounds based on the icons:
If the city displays the clear_sky icon, play the clear_sky_sound
Otherwise, If it displays the few_clouds icon, play the few_clouds_sound
Otherwise, If it displays the scattered_clouds icon, play the scattered_clouds_sound
Otherwise, If it displays the broken_clouds, play the broken_clouds_sound
Otherwise, If it displays the shower_rain icon, play the shower_rain_sound
Otherwise, If it displays the rain icon, play the rain_sound
Otherwise, If it displays the thunderstorm icon, play the thunderstorm_sound
Otherwise, If it displays the snow icon, play the snow_sound
Otherwise, If it displays the mist icon, play the mist_sound.
Here is my Fragment's code:
public class FirstFragment extends Fragment {
private WeatherDataViewModel viewModel;
MediaPlayer firstSound, secondSound, thirdSound, fourthSound, fifthSound, sixthSound, seventhSound, eightSound, ninethSound;
public FirstFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_first, container, false);
// For displaying weather data
final TextView current_temp = rootView.findViewById(R.id.textView10);
final TextView current_output = rootView.findViewById(R.id.textView11);
final TextView rise_time = rootView.findViewById(R.id.textView25);
final TextView set_time = rootView.findViewById(R.id.textView26);
final TextView temp_out = rootView.findViewById(R.id.textView28);
final TextView Press_out = rootView.findViewById(R.id.textView29);
final TextView Humid_out = rootView.findViewById(R.id.textView30);
final TextView Ws_out = rootView.findViewById(R.id.textView33);
final TextView Visi_out = rootView.findViewById(R.id.textView34);
final TextView Cloud_out = rootView.findViewById(R.id.textView35);
final ImageView current_icon = rootView.findViewById(R.id.imageView6);
final SwipeRefreshLayout realSwipe = rootView.findViewById(R.id.real_swipe);
// Get our ViewModel instance
viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);
// And whenever the data changes, refresh the UI
viewModel.getWeatherDataLiveData().observe(getViewLifecycleOwner(), data -> {
realSwipe.setOnRefreshListener(() -> {
// perform you action here for ex. add refresh screen code here
new Handler().postDelayed(() -> {
// this code is for stop refreshing icon, After 1000 ms automatically refresh icon will stop
realSwipe.setRefreshing(false);
}, 1000);
});
int drawableResource = -1; // here define default icon for example R.drawable.default_weather_icon
if (data != null) {
current_temp.setVisibility(View.VISIBLE);
current_temp.setText(data.getMain().getTemp() + " ℃"); // for that you can use strings resource and templates more in https://developer.android.com/guide/topics/resources/string-resource.html#formatting-strings
current_output.setVisibility(View.VISIBLE);
current_output.setText(data.getWeather().get(0).getDescription());
rise_time.setVisibility(View.VISIBLE);
rise_time.setText(data.getSys().getSunrise() + " ");
set_time.setVisibility(View.VISIBLE);
set_time.setText(data.getSys().getSunset() + " ");
temp_out.setVisibility(View.VISIBLE);
temp_out.setText(data.getMain().getTemp() + " ℃");
Press_out.setVisibility(View.VISIBLE);
Press_out.setText(data.getMain().getPressure() + " hpa");
Humid_out.setVisibility(View.VISIBLE);
Humid_out.setText(data.getMain().getHumidity() + " %");
Ws_out.setVisibility(View.VISIBLE);
Ws_out.setText(data.getWind().getSpeed() + " Km/h");
Visi_out.setVisibility(View.VISIBLE);
Visi_out.setText(data.getVisibility() + " m");
Cloud_out.setVisibility(View.VISIBLE);
Cloud_out.setText(data.getClouds().getAll() + " %");
// get actual weather.
String icon = data.getWeather().get(0).getIcon();
switch (icon) {
case "01d":
case "01n":
drawableResource = R.drawable.sun;
break;
case "02d":
case "021n":
drawableResource = R.drawable.few_clouds;
break;
case "03d":
case "03n":
drawableResource = R.drawable.scattered_clouds;
break;
case "04d":
case "04n":
drawableResource = R.drawable.broken_clouds;
break;
case "09d":
case "09n":
drawableResource = R.drawable.shower_rain;
break;
case "10d":
case "10n":
drawableResource = R.drawable.small_rain;
break;
case "11d":
case "11n":
drawableResource = R.drawable.thunderstorm;
break;
case "13d":
case "13n":
drawableResource = R.drawable.snow;
break;
case "50d":
case "50n":
drawableResource = R.drawable.mist;
break;
}
if (drawableResource != -1)
current_icon.setImageResource(drawableResource);
} else {
Log.e("TAG", "No City found");
current_temp.setVisibility(View.GONE);
current_output.setVisibility(View.GONE);
rise_time.setVisibility(View.GONE);
set_time.setVisibility(View.GONE);
temp_out.setVisibility(View.GONE);
Press_out.setVisibility(View.GONE);
Humid_out.setVisibility(View.GONE);
Ws_out.setVisibility(View.GONE);
Visi_out.setVisibility(View.GONE);
Cloud_out.setVisibility(View.GONE);
Toast.makeText(requireActivity(), "No City found", Toast.LENGTH_SHORT).show();
}
});
return rootView;
}
public void getWeatherData(String name) {
// The ViewModel controls loading the data, so we just
// tell it what the new name is - this kicks off loading
// the data, which will automatically call through to
// our observe() call when the data load completes
viewModel.setCityName(name);
}
}
EDIT:
DennisVA helped by providing a sample I could follow(i tried his 2nd suggestion) but when I tried it, I got some errors which I shared using a link in his comment section.
I decided to bounty this post to draw more attention, so I can be directed more in the right direction.
MediaPlayer firstSound, secondSound, thirdSound, fourthSound, fifthSound, sixthSound, seventhSound, eightSound, ninethSound;
Here you created 9 MediaPlayer objects, and from their names and their count that exactly the same as the number of icons; I realized that each one will handle the sound of a certain icon.
Although, you could only use a single MediaPlaer object for playing any of the 9 sounds you have. Think of it like your operating system media player, you have a single media player app, and you can drag and drop any media file at a time, and change it whenever you want without changing the app itself.
This is valid as long as you want to play a single media file at a time; but, in case you want to play multiple sounds at the same time, you probably need more than one MediaPlayer object.
So, as in your case you've a single whether condition at a time, so I assume that you want to play a single file at a time; and therefore you've a single icon at a time (due to the switch statement), and hence you need to change above mentioned snippet to be a single variable:
MediaPlayer mediaPlayer;
My knowledge of java dictionary is quite narrow and I find it hard to understand documentation
A dictionary (or it's a Map for accurately speaking in java), think of it like a phone directory: You know the person name (Key), and need to get the corresponding phone number (Value); the difference in this example that in phone directory there is only a unique value for each key/person; but in java Map, you can have duplicate values (i.e. more than one key/person can own the same value/phone number).
V put(K key,
V value)
Also, In java, the key and value can be almost anything and this can be indicated from the (K & V) symbols which are a generics.
I thought of using K as Icon and V as my sound but I didn't know how to apply it
In the shared simple example, I don't really think that you need a map; as the sound resources are highly coupled with the corresponding icon drawable resources, then you can just treat the sound raw files like you did with drawables.
So, whenever you change the current drawable (i.e. in the switch statement), you also need to change the current sound in the switch statement, assuming they have the same name as the drawables:
MediaPlayer mediaPlayer; // single MediaPlayer object
int drawableResource = -1; // here define default icon for example R.drawable.default_weather_icon
int soundResource = -1; // Default sound is nothing
String icon = data.getWeather().get(0).getIcon();
switch (icon) {
case "01d":
case "01n":
drawableResource = R.drawable.sun;
soundResource = R.raw.sun;
break;
case "02d":
case "021n":
drawableResource = R.drawable.few_clouds;
soundResource = R.raw.few_clouds;
break;
case "03d":
case "03n":
drawableResource = R.drawable.scattered_clouds;
soundResource = R.raw.scattered_clouds;
break;
case "04d":
case "04n":
drawableResource = R.drawable.broken_clouds;
soundResource = R.raw.broken_clouds;
break;
case "09d":
case "09n":
drawableResource = R.drawable.shower_rain;
soundResource = R.raw.shower_rain;
break;
case "10d":
case "10n":
drawableResource = R.drawable.small_rain;
soundResource = R.raw.small_rain;
break;
case "11d":
case "11n":
drawableResource = R.drawable.thunderstorm;
soundResource = R.raw.thunderstorm;
break;
case "13d":
case "13n":
drawableResource = R.drawable.snow;
soundResource = R.raw.snow;
break;
case "50d":
case "50n":
drawableResource = R.drawable.mist;
soundResource = R.raw.mist;
break;
}
if (drawableResource != -1)
current_icon.setImageResource(drawableResource);
if (soundResource != -1) {
prepareMediaPlayer(soundResource);
}
// In FirstFragment:
private void prepareMediaPlayer(int resource) {
// add track file
mMediaPlayer = MediaPlayer.create(requireActivity(), resource);
// listening to when the media file finishes playing so that we can release the resources
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
if (mMediaPlayer != null) {
mMediaPlayer.release();
}
}
});
}
And whenever you want to play the sound, just call mMediaPlayer.start(), but make sure that prepareMediaPlayer() is called in advance.
Hint: if you want to use a map, I'd recommend that you create a POJO/Data class for the Weather, so that probably we'd a Map.
I would create a wrapper to make it easier to show things to your UI
For example:
public class Weather{
#DrawableRes
private int icon; //Resource id of the drawable
private String sound; //Name or path the the sound file
public Weather(WeatherPojo pojo){
switch(pojo.icon){
case "01d":
case "01n":
this.icon = R.drawable.sun;
this.sound = ...
}
}
}
This class could also contain a play(MediaPlayer player) method
Some other things i noticed:
avoid hardcoding your strings
you might wanna have a look at viewbinding to reduce that high amount of ugly rootView.findViewById calls (https://developer.android.com/topic/libraries/view-binding)
Edit:
Another, probably better option would be to create some kind of WeatherFactory class or parser that produces subclasses of an abstract Weather class,
and have that abstract class contain code for playing the media file:
public abstract class Weather{
#DrawableRes
private int iconResId;
#RawRes
private int soundResId;
protected Weather(#DrawableRes int iconResId, #RawRes int soundResId){
this.iconResId = iconResId;
this.soundResId = soundResId
}
public void playSound(MediaPlayer mediaPlayer, Context context){
String assetFileDescriptor = context.resources.openRawResourceFd(soundResId);
if(assetFileDescriptor == null)
return;
mediaPlayer.reset();
mediaPlayer.setDataSource(assetFileDescriptor.fileDescriptor,
assetFileDescriptor.startOffset, assetFileDescriptor.declaredLength);
mediaPlayer.prepareAsync();
}
}
public class Snow extends Weather{
public Snow(){
super(R.drawable.snow, R.raw.snow)
}
}
Now u can call snow.playSound() in ur fragment or viewModel and generate getters and setters in the abstract class to get the drawable and display it.
Other variables for example the description can also be injected in the constructor of the subclass, or u can extend the subclasses themselves for the different types of snow (LightSnow, HeavySnow, ...)
Note: I haven't tested any of this code so it might need some changes here and there
Related
I have fiddled with the FirebaseRecyclerAdapter for quite some time now. It's really a great tool to populate a custom list/recycler view very fast with all of its features. However, one thing that I would like to ask is how to handle positions of items inside the adapter itself.
So for example, I want to mimic this small feature that WhatsApp has in their chats.
So, in a group chat setting, if a person sends more than one consecutive message in a row, the display name of that particular person will be invisible.
The logic behind it according to my understanding: if the person who sends the message is the same for (position - 1), then I will just make the EditText invisible for (position). This is, of course, to prevent a very long stream of text with minimum amounts of repetitive information.
Let's say the JSON tree from Firebase database is as follows.
{
"messages" : {
"pushed_id_1" : {
"message_sender" : "AppleJuice",
"message_text" : "Are you free?"
},
"pushed_id_2" : {
"message_sender" : "AppleJuice",
"message_text" : "On Saturday I mean..."
}
}
}
The FirebaseRecyclerAdapter would look like this.
FirebaseRecyclerAdapter<Message, MessageViewHolder> adapter = new FirebaseRecyclerAdapter<Message, MessageViewHolder>(Message.class, R.layout.message_item, MessageViewHolder.class, myRef) {
#Override
protected void populateViewHolder(MyBookingsViewHolder viewHolder, Booking model, int position) {
viewHolder.messageSender.setText(model.getMessage_sender());
viewHolder.messageText.setText(model.getMessage_text());
//put some code here to implement the feature that we need
}
};
messages_recycler_menu.setAdapter(adapter);
The furthest I have gone is to use getItemCount() method in the FirebaseRecyclerAdapter, but I am still unable to achieve the feature that mimics that of Whatsapp's that I was talking about previously.
Is there a method that can achieve this? Or am I missing something very important in this example?
String lastSender=null; //or some random string
FirebaseRecyclerAdapter<Message, MessageViewHolder> adapter =
new FirebaseRecyclerAdapter<Message, MessageViewHolder>
(Message.class, R.layout.message_item, MessageViewHolder.class, myRef) {
#Override
protected void populateViewHolder(MyBookingsViewHolder viewHolder, Booking model, int position) {
if (model.getMessage_sender().equals(lastSender){ //check if the current sender is same as the last sender
viewHolder.messageText.setText(model.getMessage_text()); //setting only message text
viewHolder.messageSender.setVisibility(View.GONE); //if required
}else{
lastSender=model.getMessage_sender();//updating the lastSender value
viewHolder.messageSender.setVisibility(View.VISIBLE); //if required
viewHolder.messageSender.setText(model.getMessage_sender());
viewHolder.messageText.setText(model.getMessage_text());
}
//put some code here to implement the feature that we need
}
};
messages_recycler_menu.setAdapter(adapter);
As discussed in comments:
Let's suppose I received message and stored sender's name in constant String that should be static constant in some class i.e. AppConstants so that It can be accessed everywhere therefore after that:
in populateViewHolder or in your message receiver do something like this:
if (TextUtils.isEqual(storedSender,model.getMessage_sender())){
viewHolder.messageSender.setVisiblity(View.GONE)
}
else{
// do your normal flow
viewHolder.messageSender.setVisiblity(View.VISIBLE);
storedSender = model.getMessage_sender();
}
In this way automatically the last message's sender's name will be updated , this is exactly what you were trying to achieve by adapter position!
I am trying to develop a feature for an app where you have a list of "codes" that you enter and that are dialed one by one. I have looked over TelephonyManager and followed a tutorial on developing a broadcast receiver with a listener for responses but it does not always work as it should.
One idea was to store all the numbers necessary in SharedPref. If the Activity (only created for the intent towards dialer) went into onStop() (meaning that above, the dialer screen was on) and then into onResume() (call ended and activity resumed), I would remove the number last dialed from the SharedPref and then, if any remained, open the dialer again. The broadcast made sure than once the state flow of the TelephonyManager was "OFFHOOK => IDLE", it would return the user to the Activity . Short story, it did not always perform as it should have.
How should I tackle the problem ?
EDIT
My curent solution was to
Create a doPhoneCall() function that would handle the intent creation and deployment itself.
#Override
protected void doPhoneCall(){
super.onResume();
wentIntoCall = false;
/** More code here for dialing */
}
Place this function into the onResume(). Even if the onResume will be called multiple times, the wentIntoCall boolean will make sure that the function won't be called multiple times.
#Override
protected void onResume() {
super.onResume();
if(wentIntoCall)
doPhoneCall();
}
Having in mind that after a call, the phone should return to its previous state, so it would return to the Activity in which we are doing are call, we will add to the activity a CallListener, and in the case of IDLE, based on the tutorial linked above, we make the wentIntoCall be true. ( The activity will go into onResume() and, upon seeing that the boolean is true, it will initialize the next call ).
case TelephonyManager.CALL_STATE_IDLE:
Log.e(TAG, "CALL_STATE_IDLE==>"+incoming_number);
if((prev_state == TelephonyManager.CALL_STATE_OFFHOOK)){
prev_state=state;
wentIntoCall = true;
//Answered Call which is ended
}
if((prev_state == TelephonyManager.CALL_STATE_RINGING)){
prev_state=state;
wentIntoCall = true;
//Rejected or Missed call
}
My final question : is this the right way to handle this functionality, or should I try to come up with another implementation of it ?
EDIT 2
Looks like my "codes", being USSD codes, are not behaving like normal phone calls.. So for normal phone calls the code above seems to work, but for dialing codes, not that much. I have "downgraded" my solution to a simple for-loop. Seems to be working fine now.
I dont know for android O , but for android 6.0 > You cant detect answer in direct way . Call no exist number and see PhoneStateListener what will trigger in one case and track successed call also.
Make public static array , add all your numbers intro array .
I made services . Insert permissions in manifest make your own action also ( NEXT_CALL for example ) .
Than easy make intent for startServices :
SharedPreferences settings;
SharedPreferences.Editor SAVES;
Intent serviceIntent = new Intent(MainActivity.this, ServiceForCalls.class);
serviceIntent.setAction("xxx.xxx.NEXT_CALL");
startService(serviceIntent);
isCalling = true;
SAVES.putBoolean( "isCalling" , isCalling );
SAVES.commit();
SAVES.apply();
You must use timeout interval about 10 sec for next call.
Heres little help func - end call and phoneState handler :
void END_CALL () throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
Class c = null;
try {
c = Class.forName(tm.getClass().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Method m = null;
try {
m = c.getDeclaredMethod("getITelephony");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
m.setAccessible(true);
Object telephonyService = m.invoke(tm); // Get the internal ITelephony object
c = Class.forName(telephonyService.getClass().getName()); // Get its class
m = c.getDeclaredMethod("endCall"); // Get the "endCall()" method
m.setAccessible(true); // Make it accessible
m.invoke(telephonyService); // invoke endCall()
if ( SIGNAL_STOP == false ) {
timerHandlerServicesStartNewNumber.postDelayed(timerRunnableServicesStartNewNumber, 1000);
}
}
private class PhoneStateChangeListener extends PhoneStateListener {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
switch(state){
case TelephonyManager.CALL_STATE_RINGING:
Log.println( Log.INFO , "RINGING" , "SERVICES%%%%%%%%%%%%%%%%RINGING%%%%%%%%%%%%%%%%%%");
wasRinging = true;
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
Log.println( Log.INFO , "OFFHOOK BROADCAST" , "SERVICES%%%%%%%%%%%%%%%%%%%%%%%%");
if (!wasRinging) {
// Start your new activity
Log.println( Log.INFO , "OFFHOOK BROADCAST" , "SERVICES%%%%%%%%%%%%%%%%%%%%%%%");
if (SIGNAL_STOP == false) {
timerHandlerServices.postDelayed(timerRunnableServices, 10000);
}
} else {
// Cancel your old activity
Log.println( Log.INFO , "OFFHOOK BROADCAST" , "SERVICES%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
}
// this should be the last piece of code before the break
wasRinging = true;
break;
case TelephonyManager.CALL_STATE_IDLE:
Log.println( Log.INFO , "IDLE BROADCAST" , "SERVICES%%%%%%%%%%%%%%%%%IDLE%%%%%%%%%%%%%%%%%%%");
// this should be the last piece of code before the break
wasRinging = false;
break;
}
}
}
Thanks for : "meaning that above, the dialer screen was on" nice catch.
As a side note, in the Android O developer preview there is a new API which allows you to send a USSD request and register a callback to receive its results. For newer versions of Android this might meet your needs better.
I had an issue where Text to Speech would not speak anything. I realised this was due to the fact that I was attempting to call 'Speak()' before TTS had initialised.
I need to wait until TTS has initialised, so that I can call 'Speak()' successfully. I thought doing something along the lines of this would work:
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
mTTSInitialised = true;
} else {
Log.e("TTS", "Initialisation Failed!");
}
}
...
while(!mTTSInitialised){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
But this fails to initialise at all. Is there a way to do this effectively?
The initialisation of the Text to Speech engine is asynchronous, which is why you realised you have to 'wait' for it to complete, before requesting that it processes an utterance.
Even when it eventually initialises successfully, it can be subsequently killed by the system, or it can of course fail to initialise, so you always need to be ready to handle a request to speak, where the engine isn't prepared.
Add the following helper class
public class PendingTTS {
private String pendingUtterance;
private int pendingQueueType;
public String getPendingUtterance() {
return this.pendingUtterance;
}
public void setPendingUtterance(#NonNull final String pendingUtterance) {
this.pendingUtterance = pendingUtterance;
}
public int getPendingQueueType() {
return this.pendingQueueType;
}
public void setPendingQueueType(final int pendingQueueType) {
this.pendingQueueType = pendingQueueType;
}
}
Assuming you're using an Activity, you need to declare the following variables:
private volatile PendingTTS pendingTTS;
private static final int MAX_INIT_ATTEMPTS = 4;
private volatile int initCount;
and initialise the Text to Speech object in onCreate()
tts = new TextToSpeech(YOURActivity.this, YOURonInitListener);
In your onInitListener you would check if there is any pending speech:
#Override
public void onInit(final int status) {
switch (status) {
case TextToSpeech.SUCCESS:
initCount = 0;
// Set up tts stuff
tts.setOnUtteranceProgressListener(YOURprogressListener);
if (pendingTTS != null) {
// We have pending speech, process it and check the result
int speechResult = tts.speak(pendingTTS.getPendingUtterance(),pendingTTS.getPendingQueueType(),
// remaining tts variables here)
switch (speechResult){
case TextToSpeech.SUCCESS:
// Result was successful
pendingTTS = null;
break;
case TextToSpeech.ERROR:
// Speech failed
// Check if it has repeatedly failed up to the max attempts
if(initCount < MAX_INIT_ATTEMPTS){
initCount ++;
tts = new TextToSpeech(YOURActivity.this, YOURonInitListener);
} else {
// Totally broken - let the user know it's not working
}
break;
}
} else {
// there was nothing to process
}
break;
case TextToSpeech.ERROR:
// Check if it has repeatedly failed up to the max attempts
if(initCount < MAX_INIT_ATTEMPTS){
initCount ++;
tts = new TextToSpeech(YOURActivity.this, YOURonInitListener);
} else {
// Totally broken - let the user know it's not working
}
break;
}
I've glued the above together from my code - where the speech and initialisation methods are all separated, but I tried to give you an overview above of everything you need to handle.
Elsewhere in your code, when you make a tts.speak(//stuff here) request, you need to check the result as demonstrated above, to make sure it was successful. Again, in my code, this is separated into one single method. If it does fail, you need to set the PendingTTS parameters prior to attempting to initialise again:
pendingTTS = new PendingTTS();
pendingTTS.setPendingQueueType(// your queue type);
pendingTTS.setPendingUtterance(// your utterance);
It is is successful, make sure pendingTTS is set to null.
The overall design is that if the initialisation failed, it will attempt to initialise again, up to the maximum allowed attempts. If the speech fails, it will attempt to initialise the engine again, firstly setting the PendingTTS parameters.
Hope you managed to follow that.
Hmm..
Not a very good idea.
You can try to add the text to the TTS queue and let it do it's work. This snippet can be inside button click, etc as:
tts.speak(toSpeak, TextToSpeech.QUEUE_ADD, null);
Small tutorial that would help.
In the docs here I'm following how to receive location requests and two unusual things I noticed are in these two blocks of code:
result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
#Override
public void onResult(LocationSettingsResult result) {
final Status status = result.getStatus();
final LocationSettingsStates = result.getLocationSettingsStates(); //<--This line I don't understand
switch (status.getStatusCode()) {
case LocationSettingsStatusCodes.SUCCESS:
// All location settings are satisfied. The client can initialize location
// requests here.
...
break;
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
// Location settings are not satisfied. But could be fixed by showing the user
// a dialog.
try {
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
status.startResolutionForResult(
OuterClass.this,
REQUEST_CHECK_SETTINGS);
} catch (SendIntentException e) {
// Ignore the error.
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
// Location settings are not satisfied. However, we have no way to fix the
// settings so we won't show the dialog.
...
break;
}
}
});
And in the second block:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
final LocationSettingsStates states = LocationSettingsStates.fromIntent(intent); //<--And this line
switch (requestCode) {
case REQUEST_CHECK_SETTINGS:
switch (resultCode) {
case Activity.RESULT_OK:
// All required changes were successfully made
...
break;
case Activity.RESULT_CANCELED:
// The user was asked to change settings, but chose not to
...
break;
default:
break;
}
break;
}
}
I've added some arrows pointing to the lines I don't understand. In particular, these two:
final LocationSettingsStates = result.getLocationSettingsStates();
final LocationSettingsStates states = LocationSettingsStates.fromIntent(intent);
The first line is something I haven't seen before. How is this valid, assigning a value to a data type? Then that class is no longer used anywhere else in that block of code, so what's the purpose of the assignment?
In the other line, now it's assigning a value to an instance called states of that data type but that instance is not used anywhere else in onActivityResult().
So what is going on here? Thanks.
The first line is definitely a typo; it should be something like:
final LocationSettingsStates states = result.getLocationSettingsStates();
And yeah, it isn't used in either place, which is weird, but you can call things like isBleUsable() on it to determine what exactly is present and usable right now. In the latter case, it's what's usable after the attempted resolution.
I need some help with debugging my application. First of all: In emulator and on some other devices my app is running fine. On my device I got a force close (without a force close message).
The "crash" happens if the Activity of the app is changed.
Here is some code of the MainActivity class. It just reads html content from a web page over webview. And no, it is NOT possible to do this over HttpRequest because I was not able to simulate the post request.
public class MainActivity extends Activity {
public final static String EXTRA_HTML = "com.example.com.test.HTML";
private WebView mWebView;
private ProgressDialog mDialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webView1);
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
mWebView.setBackgroundColor(0);
mWebView.setWebChromeClient(new WebChromeClient() {
public boolean onConsoleMessage(ConsoleMessage cmsg) {
if (cmsg.message().startsWith("MAGIC")) {
mDialog.cancel();
/*HashMap<String, String> message = new HashMap<String, String>();*/
String msg = cmsg.message().substring(5);
Intent intent = new Intent(MainActivity.this,
ReadDataActivity.class);
/*message.put("message", msg);*/
/*intent.putExtra(EXTRA_HTML, message);*/
intent.putExtra(EXTRA_HTML, msg);
startActivity(intent);
}
return false;
}
});
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setPluginState(PluginState.OFF);
mWebView.getSettings().setLoadsImagesAutomatically(false);
mWebView.getSettings().setBlockNetworkImage(true);
mWebView.getSettings().setAppCacheEnabled(true);
mWebView.getSettings().setSavePassword(true);
mWebView.getSettings()
.setCacheMode(WebSettings.LOAD_NORMAL);
mWebView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String address) {
if (address.indexOf("mySession") != -1) {
view.loadUrl("javascript:console.log('MAGIC'+document.getElementsByTagName('html')[0].innerHTML);");
}
});
mWebView.loadUrl("http://www.myurl.de");
}
So, in the onConsoleMessage() method I just pass the html code to another Activity class which read, parse and display the content.
The problem is now that at this point when the ReadDataActivity class should be loaded the application just close and go back to the home screen without any message or user dialog.
Is it possible that the html code which is passed as a string to the ReadDataActivity is to big? I also try to add the html code as a string in a HashMap but the problem is the same.
Some ideas what I can do to debug the problem? Maybe I should try to create a Parcelable object?
In the emulator everything is working fine.
As per my experience (sometime ago), you are able to parcel up to 1MB of data in a Bundle for IPC. This limit can be reduced if a lot of transactions are happening at a given time. Further information here.
In order to overcome this issue, I would suggest you to save your content on a temp file and pass the path/URI of your temp file to your second activity. Then in your second activity, read the contents out from file, perform your desired operations and finally delete that file.
If you want, you may also incorporate Shared_Preferences for this task - if you think handling files is cumbersome.
I did some research on the maximum amount of data you can transfer using an Intent. And it seems that the limit is nowhere near 1MB or 90KB, it's more like 500KB (tested on API 10, 16, 19 and 23).
I wrote a blog post about this topic, you can find it here: http://web.archive.org/web/20200217153215/http://neotechsoftware.com/blog/android-intent-size-limit
The size limit of Intent is still pretty low in Jelly Bean, which is somewhat lower than 1MB (around 90K), so you should always be cautious about your data length, even if your application targets only latest Android versions.
I have seen that by writing and reading from a file consists of less performance .
Then I have seen this solution : . So I am using this solution :
public class ExtendedDataHolder {
private static ExtendedDataHolder ourInstance = new ExtendedDataHolder();
private final Map<String, Object> extras = new HashMap<>();
private ExtendedDataHolder() {
}
public static ExtendedDataHolder getInstance() {
return ourInstance;
}
public void putExtra(String name, Object object) {
extras.put(name, object);
}
public Object getExtra(String name) {
return extras.get(name);
}
public boolean hasExtra(String name) {
return extras.containsKey(name);
}
public void clear() {
extras.clear();
}
}
Then in MainActivity I have called it like the following :
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
extras.putExtra("extra", new byte[1024 * 1024]);
extras.putExtra("other", "hello world");
startActivity(new Intent(MainActivity.this, DetailActivity.class));
and in DetailActivity
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
if (extras.hasExtra("other")) {
String other = (String) extras.getExtra("other");
}
The fixed size of 1MB is not only limited to intents. As Intents, Content Providers, Messenger, all system services like Telephone, Vibrator etc. utilize IPC infrastructure provider by Binder. Moreover the activity lifecycle callbacks also use this infrastructure.
1MB is the overall limit on all the binder transactions executed in the system at a particular moment.
In case there are lot of transactions happening when the intent is sent,it may fail even though extra data is not large.
http://codetheory.in/an-overview-of-android-binder-framework/
A little late to the game, but I just ran up against the same issue. Writing the data to file didn't really make sense performance-wise in my case, but I came across this in my search for answers:
http://developer.android.com/guide/faq/framework.html#3
Using a singleton is better for me as there's no need for disk IO. Better performance if the data doesn't need to be persisted.
Here's an example implementation:
public class DataResult {
private static DataResult instance;
private List<SomeObject> data = null;
protected DataResult() {
}
public static DataResult getInstance() {
if (instance == null) {
instance = new DataResult();
}
return instance;
}
public List<SomeObject> getData() { return data; }
public void setData(List<SomeObject> data) { this.data = data; }
}
Then you can set using this in one activity:
DataResult.getInstance().setData(data);
And get it in the other activity like this:
List<SomeObject> data = DataResult.getInstance().getData();
The Binder transaction buffer has a limited fixed size - 1Mb.
But the problem is that buffer shared by all transactions in progress for the process.
So try to keep your intent's data as small as possible every time.
The use of static String variable is good. If there is a need for the user to go back & forth between different pieces of HTML, you can also use LruCache like this:
static LruCache<String, String> mMemoryCache;
final int kiloByte = 1024;
.
.
final int maxMemoryKB = (int) (Runtime.getRuntime().maxMemory() / kiloByte);
// then choose how much you want to allocate for cache
final int cacheSizeKB = maxMemoryKB / 8;
.
.
mMemoryCache = new LruCache<String, String>(cacheSizeKB) {
//#Override
protected int sizeOf(String key, String value) {
try {
byte[] bytesUtf8 = value.getBytes("UTF-8");
return bytesUtf8.length / kiloByte;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return -1;
}
};
.
.
String cacheKey = generateUniqueString(key);
if (mMemoryCache.get(key) == null) {
mMemoryCache.put(cacheKey, yourContent);
}
Intent intent = new Intent(getApplicationContext(), ReadDataActivity.class);
intent.putExtra(EXTRA_HTML, cacheKey);
startActivity(intent);
Then on the ReadDataActivity side
Intent intent = getIntent();
String cacheKey = intent.getStringExtra(EXTRA_HTML);
String contentString = MainActivity.mMemoryCache.get(cacheKey);
doSomethingWith(contentString);
This idea came from here.
An alternative solution for passing large data between activities is to use a static field. In your case add this line to ReadDataActivity class
public static String msg;
Then you can use the static field msg within MainActivity class as follows
ReadDataActivity.msg = cmsg.message().substring(5);
And finally start your activity without extra put
Intent intent = new Intent(MainActivity.this, ReadDataActivity.class);
startActivity(intent);