I have a strange issue, which I'm hoping I can explain well enough. My app has two activities - MainActivity and SearchActivity. I have a button on MainActivity which triggers an upload from the database on the device to a remote database on my web server. If I click the button when I first launch the app, no problem, works fine. If I switch to the SearchActivity, don't do anything, and switch back, then try the button, the app crashes with a ConcurrentModificationException.
I've got an AsyncTask which sends the contents of a local database (already pulled out of the database and sent to the thread through the parameters as an ArrayList). I've spent hours debugging this and still can't work out where it is. Any suggestions would be greatly appreciated.
This is the code triggered on the button press, to request the contents of the database from a separate Databaser thread
Button btnRemoteSync = (Button)findViewById(R.id.btnSync);
btnRemoteSync.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent startUpload = new Intent(getString(R.string.broadcast_search_database));
startUpload.putExtra("type-id",1);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startUpload);
}
});
This is the code in the BroadcastReceiver which gets each response from the databaser and adds them to an ArrayList of custom ResponseObjects. When the databaser thread sends a bssid value of DONE, the AsyncTask is launched with the ArrayList passed in as a parameter.
#Override
public void onReceive(Context context, Intent intent) {
String bssid = intent.getStringExtra(getString(R.string.data_bssid));
if (bssid.equals("DONE")) {
RemoteDatabaseUploader rdb = new RemoteDatabaseUploader(getApplicationContext());
rdb.execute(databases);
} else {
databases.add(new ResponseObject(getApplicationContext(),
bssid,
intent.getStringExtra(getString(R.string.data_ssid)),
intent.getStringExtra(getString(R.string.data_capabilities)),
intent.getIntExtra(getString(R.string.data_level), 0),
intent.getIntExtra(getString(R.string.data_frequency), 0),
intent.getStringExtra(getString(R.string.data_timestamp)),
intent.getDoubleExtra(getString(R.string.data_latitude), 0),
intent.getDoubleExtra(getString(R.string.data_longitude), 0)));
}
}
Below is the doInBackground code for the AsyncTask
#Override
protected Integer doInBackground(ArrayList<ResponseObject>... params) {
ArrayList<ResponseObject> entries = params[0];
try {
URL url = new URL(insertURL);
for (Iterator<ResponseObject> it = entries.iterator(); it.hasNext();) {
ResponseObject ro = it.next(); // THIS IS WHERE THE EXCEPTION REFERENCES IN THE DEBUG OUTPUT
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("USER-AGENT", "Mozilla/5.0");
urlConnection.setRequestProperty("ACCEPT-LANGUAGE", "en-US,en;0.5");
urlConnection.setDoOutput(true);
String postParams = "bssid=" + ro.BSSID
+ "&ssid=" + ro.SSID
+ "&capabilities=" + ro.CAPABILITIES
+ "&level=" + String.valueOf(ro.LEVEL)
+ "&frequency=" + String.valueOf(ro.FREQUENCY)
+ "×tamp=" + ro.TIMESTAMP
+ "&lat=" + String.valueOf(ro.LAT)
+ "&long=" + String.valueOf(ro.LON);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(postParams);
wr.flush();
wr.close();
Log.d("RemoteDatabase : ", "Post sent " + ro.BSSID + " || " + String.valueOf(urlConnection.getResponseCode()));
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
entries.clear();
return null;
}
EDIT -- I appear to have traced the issue down to another section of code, where a broadcast is sent on clicking the button. The button definitely only sends once (been checking using Log.d) but the received in the databaser is receiving it twice. Trying to fix this now.
It's been a while but realised I forgot to add how I solved this, just in case it is a help to anyone else.
I found the issue here was actually that some Android devices send multiple copies of a broadcast. I was using an HTC handset for testing and apparently for some reason they send 2 copies of all broadcasts. The way my code was working I was spawning a thread off a broadcast, which was resulting in 2 identical threads operating on the same data. When these completed they were each sending their "Done" broadcast, which was resulting in 4 of them being received by the main thread. Complete mess.
I ended up having to add a unique ID token to each broadcast and record the values at the receiving end, so if the same ID was received twice no action would be taken the second time.
Please insert a progress bar in your onPreExecute() method of async task and dismiss it in onPostExecute().I thing it is taking too much time to complete the async task,and you are tapping the button again prior to the completion of the async task.
Related
My problem is that in my app not show notification.
My application does that each time a button is pressed I create new thread and show notification with info that thread is running or waiting (this works fine). Then, if the thread is running, it will randomly sleep for 5-10 seconds and get data from the rest api and a notification should be displayed that the thread is finished (this notification is not displayed).
Fineshed notifications show after i press again button. As you can see in the image.
Image:
constructor view:
public MainView() {
Button ipButton = getIpButton();
setMargin(true);
setHorizontalComponentAlignment(Alignment.START, ipButton);
add(ipButton);
}
button:
private Button getIpButton() {
final UI ui = UI.getCurrent();
final VaadinSession session = VaadinSession.getCurrent();
Button ipButton = new Button("My IP");
AtomicInteger orderIndex = new AtomicInteger();
ipButton.addClickListener(_e -> {
int orderThread = orderIndex.getAndIncrement();
openBeginNotification(orderThread);
executor.submit(() -> {
try {
UI.setCurrent(ui);
VaadinSession.setCurrent(session);
long sleepTime = (long) (Math.random() * (10 - 5) + 5);
System.out.printf("%d: %ds\n", orderThread, sleepTime);
Thread.sleep(sleepTime * 1000);
IpDTO ip = restTemplate.getForObject("http://ip.jsontest.com/", IpDTO.class);
System.out.printf("%d: %s\n", orderThread, ip);
try {
VaadinSession.getCurrent().lock();
getFinishNotification(orderThread).open(); // here not show notification
VaadinSession.getCurrent().unlock();
} catch (Exception e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
});
return ipButton;
}
notification methods:
private void openBeginNotification(int orderThread) {
Notification notification;
if (executor.getActiveCount() == MAX_THREADS) {
// thread is in front
notification = getWaitNotification(orderThread);
} else {
// thread run
notification = getRunNotification(orderThread);
}
notification.open();
}
private Notification getRunNotification(int orderThread) {
return getNotification("Task " + orderThread + ": run", NotificationVariant.LUMO_PRIMARY);
}
private Notification getWaitNotification(int orderThread) {
return getNotification("Task " + orderThread + ": wait", NotificationVariant.LUMO_CONTRAST);
}
private Notification getFinishNotification(int orderThread) {
return getNotification("Task " + orderThread + ": finish", NotificationVariant.LUMO_SUCCESS);
}
private Notification getNotification(String notificationText, NotificationVariant variant) {
Notification notification = new Notification(notificationText, 1000);
notification.addThemeVariants(variant);
return notification;
}
First, you need to enable #Push to make Vaadin open a websocket connection that makes it possible for the server to directly send messages to the browser without waiting for the browser to send a message asking for changes (which happens when you click a button). The #Push annotation should be in different location depending on the Vaadin version you're using, so please refer to documentation to find the right place.
Second, please use UI::access instead of manually doing setCurrent and locking. While I didn't spot anything in your example that would break the happy case, there are still also a whole bunch of edge cases that you'd need to take into account. As an example, you're not cleaning up after setCurrent which might cause memory leaks and you're not unlocking in case something related to the notification throws an exception.
I am trying to continuously poll an NFC tag using an android phone (OnePlus 6T if it's relevant). When my tag is only powered by NFC, I get null values even though the debugger shows the payload being not null and supposedly correctly formatted (screenshot below).
Debugger output when getNdefMessage returns null
Debugger output when the returned value is not null
The code I am using to poll the tag, based on Continuously detect an NFC tag on Android and Is it possible to read an NFC tag from a card without using an intent?
protected void onResume() {
super.onResume();
if (nfcAdapter != null) {
if (!nfcAdapter.isEnabled()) {
Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
startActivity(intent);
}
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);
nfcAdapter.enableReaderMode(this, new NfcAdapter.ReaderCallback() {
#Override
public void onTagDiscovered(Tag tag) {
Log.d("Tag", "New tag detected, attempting connect");
readMessage(tag);
tagInRange = true;
}
},
NfcAdapter.FLAG_READER_NFC_A
| NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS, options);
}
}
Read message function, msg variable is a private volatile NdefMessage:
private void readMessage(Tag tag) {
Log.d("Tag", "Starting thread");
new Thread(() -> {
try {
Thread.sleep(1800);
} catch (InterruptedException e) {
e.printStackTrace();
}
final Ndef ndef = Ndef.get(tag);
Log.d("Tag", "New tag detected, attempting connect");
while (tagInRange) {
try {
ndef.connect();
msg = ndef.getNdefMessage();
Log.d("Tag", "Running on UI thread");
if (msg == null) {
Log.d("Tag", String.valueOf(ndef));
continue;
}
runOnUiThread(() -> {
parseMessage(msg);
dataElapsedTime += 0.2;
});
} catch (IOException | FormatException e) {
e.printStackTrace();
tagInRange = false;
System.out.println("Tag Connection Lost");
}
finally {
try {
ndef.close();
Log.d("tag", "Tag connection closed successfully");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
Does the getNdefMessage function use some sort of lazy loading? Or is this the result of multiple threads being spawned and some strange race condition occurring?
Update 2
Looking at the source of the ndef object the mNdefMsg you highlighted is the cached Ndef message that the System Reads before passing you the Tag object
As you seem have a custom behaving tag and may be this tag won't allow you to read the same data twice without going through a disconnect/connect mechanism.
Based on you debug data I would use ndef.getCachedNdefMessage() to read this data you are highlighting but this might not get you want you want.
Some Background, when the Android NFC service detects a Tag normally it tries to read and Ndef message from it as it might need to parse the Ndef message itself.
When you call ndef.getNdefMessage() it actually re-reads the data directly from the Tag and ignores what the System NFC service has previous read.
I would try enabling FLAG_READER_SKIP_NDEF_CHECK to stop the system reading the Ndef Message and using up you one chance to read the custom Tag .
So try add that flag e.g.
nfcAdapter.enableReaderMode(this, new NfcAdapter.ReaderCallback() {
#Override
public void onTagDiscovered(Tag tag) {
Log.d("Tag", "New tag detected, attempting connect");
readMessage(tag);
tagInRange = true;
}
},
NfcAdapter.FLAG_READER_NFC_A
| NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
| NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS, options);
Original
I think the problem is your ndef.close() is in the wrong place.
You should not close the tag unless you never want to communicate with it again.
At the moment on the first iteration of the while loop, it does the try block with the ndef.connect(); and the finally is always executed after the code in the preceding try block.
Really you should only ndef.close() outside the while block.
Adjusted code (You should also catch the TagLostException from getNdefMessage )
private void readMessage(Tag tag) {
Log.d("Tag", "Starting thread");
new Thread(() -> {
try {
Thread.sleep(1800);
} catch (InterruptedException e) {
e.printStackTrace();
}
final Ndef ndef = Ndef.get(tag);
Log.d("Tag", "New tag detected, attempting connect");
try {
ndef.connect();
} catch (IOException e) {
e.printStackTrace();
tagInRange = false;
System.out.println("Failed to Connect");
}
while (tagInRange) {
try {
msg = ndef.getNdefMessage();
Log.d("Tag", "Running on UI thread");
if (msg == null) {
Log.d("Tag", String.valueOf(ndef));
continue;
}
runOnUiThread(() -> {
parseMessage(msg);
dataElapsedTime += 0.2;
});
} catch (TagLostException | IOException | FormatException e) {
e.printStackTrace();
tagInRange = false;
System.out.println("Tag Connection Lost");
}
}
// End of while loop try close just on the safe side
try {
ndef.close();
Log.d("tag", "Tag connection closed successfully");
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
Note you might think the raw tag debug output still has an Ndef Message in it, but as you have not disabled FLAG_READER_SKIP_NDEF_CHECK then this is probably the cached data for ndef.getCachedNdefMessage()
If you look at the docs for ndef.getCachedNdefMessage you will see that it says that it causes no RF activity as this just returns the NDEF message that the System NFC read when it first detected the Tag and passed to you in the Tag Object.
Where as getNdefMessage cause I/O (RF) activity and reads the current Ndef message at time of calling (which might be different to the one the system read previously)
Update
I seem to remember you cannot call connect() multiple times and and the docs say "Only one TagTechnology object can be connected to a Tag at a time. " even though it is the same Tag tech it is probably not the same instance, so in the example I've moved connect out of the while loop.
This issue was finally resolved by understanding the following things:
The mNdefmessage variable in the ndef object corresponds to the cached message and would remain unchanged as long as the same tag instance is connected. I was reading the object wrong and presuming that that is the message I want. Thanks to Andrew for pointing this out!
The null I was receiving was not a result of where the connect/close were called and the android code needed minimal changes from what was initially posted. In the documentation for the function getNdefMessage(), it says, function may return null if tag is initialised stage. This turned out to be the real issue, which was only spotted when I set my tag (NHS3152) to blink an LED when starting up and noticed it was restarting several times after taking a set of readings from a connected resistor which shouldn't be happening (this also led to a lot of tag lost/null object exceptions).
Since the documentation on this is scanty, the reason the tag was restarting/reset turned out to be its inability to handle voltages over a certain threshold on its output pins when powered by the NFC field only and connected to a load. This led to the strange scenario of everything working just fine when the tag was being read while connected to a debugging board (LPCLink2) or being powered by the NFC but not connected to a load. This was finally addressed by changing the output voltage on the DAC pin.
I'm trying to play music in my app and while the media player is loading I want to allow the user to keep on using the app, but the app gets stuck for few seconds every time I start loading the media player, and when the media player is finished loading, only then the app returns to normal and starts working again, sometimes it's not just freezing, it also shows popup menu from the OS that prompts the user to quit the app.
I couldn't find any solution in Google or YouTube, anyone knows what's wrong with my code?
final Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
try {
String STREAM_URL = #####; // here i put the URL of the song
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mediaPlayer.setDataSource(STREAM_URL);
mediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
} catch (NullPointerException e) {
Log.d(TAG, "run: NullPointerException = " + e.getMessage());
FirebaseCrash.log("Tag = " + TAG + "run: NullPointerException = " + e.getMessage());
}
}
}
};
handler.post(runnable);
Even though you are creating a Handler, the creation of the MediaPlayer still happens on the main UI thread. You should call prepareAsync or use an AsyncTask or some other means to avoid calling prepare on the main thread.
From the documentation for Handler:
When you create a new Handler, it is bound to the thread / message
queue of the thread that is creating it
If you are streaming music from the network, preparing the media for playback is especially going to take a while. One option may be to call prepareAsync instead of calling prepare. In that case, you should set the OnPreparedListener, and in that callback call start.
I am making a simple Android Wear app to control my thermostats, and I'm sending POST requests with Volley to control them. Everything works great in the Android Wear simulator (the request works), but, while the app does load on my Moto 360, the volley request gets called but invariably times out.
Why could my volley request be failing on my watch but working on the simulator? Other apps' requests succeed on my watch (for example, the built-in weather app can load up weather data in about 3 seconds). And, the weirdest part: I had the app working (successfully making volley requests) on my watch, and, about a day after I installed it to my watch from Android Studio, it suddenly stopped loading data for no apparent reason.
What I've tried so far:
I have requested the Internet permission in my manifest.xml.
I have increased the timeout to 30 seconds (see my code below), which didn't change anything.
I have tried tethering my computer and the simulator to my phone's connection via Bluetooth (to replicate the Bluetooth connection my physical watch has to my phone), and the simulator made the request successfully still (albeit with a two-second delay), ruling out the possibility of Bluetooth being too slow.
I made sure the API level is low enough for my Marshmallow-running watch (my watch and the app are both API level 23).
I tried doing a quick test request to Google before the request to the company's servers with my thermostat data, and while the Google request returns the site's HTML code in the simulator, it times out on my watch (thirty seconds after the request is initiated).
I tried putting some dummy data into the recycler view data should be loaded into, and the dummy data indeed showed up, ruling out that the recycler view is broken.
I deleted the app from my watch and reinstalled it, and deleted the companion from my phone, reinstalled it, and deleted it again, all to no avail.
A lengthy chat with Google Support did not produce anything meaningful.
Here's my code (from my main view's adapter):
public void refreshThermostatsRecyclerView(RequestQueue queue) {
String url = "https://mobile.skyport.io:9090/login"; // login call to the thermostats server Skyport
Log.w("myApp", "Starting /login call to Skyport"); // this gets called on simulator and watch
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
Response.Listener<String>() {
#Override
public void onResponse(String response) {
// Display the response string.
Log.w("myApp", "Response is: " + response); // this gets called on the simulator but not the watch
try {
// there's some code to parse the data.
} catch (JSONException e) {
Log.w("myApp", "catching an error parsing the json."); // never gets called.
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.w("myApp", "Skyport request didn't work! " + error); // this always gets called on the watch, with the error being a timeout error (com.Android.Volley.timeouterror) but never gets called in the simulator
}
}) {
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> m = new HashMap<>();
m.put("Referer", "app:/VenstarCloud.swf");
// here I put some more headers
return m;
}
#Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> m = new HashMap<>();
m.put("version", "3.0.5");
m.put("email", userEmail);
m.put("password", userToken);
return m;
}
};
// Add the request to the RequestQueue.
int socketTimeout1 = 30000; // times out 30 seconds after the request starts on the watch
RetryPolicy policy1 = new DefaultRetryPolicy(socketTimeout1, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
stringRequest.setRetryPolicy(policy1);
queue.add(stringRequest);
}
Which is called from the onCreate() method in my Main Activity with this code:
RequestQueue queue = Volley.newRequestQueue(this);
refreshThermostatsRecyclerView(queue);
If you'd like to view the logs created by running this in the simulator and on the watch, they're on Google Drive here.
Edit 1: A reboot of my watch fixes the issue temporarily and allows the watch to make HTTP Requests again, but it breaks again once the watch disconnects from Bluetooth, connects to WiFi, disconnects from WiFi, and reconnects to Bluetooth (so it breaks every time I go across my apartment without my phone and then return).
Edit 2: I switched the volley requests all over to HTTPURLConnection Requests in an Async thread, and the same issues occur as with volley.
tl;dr: My app's Volley requests are working in the simulator but not on my Android Wear watch anymore (though Play Store-downloaded apps' similar requests work), how can I get a volley request to work again on my app on the watch?
As per these two conversations below, it seems that WiFi connectivity only allows Android Wear to connect to a phone over WiFi and not directly to the Internet. However, Android Wear 2.0 lets you use regular network APIs.
Direct internet connection on Android Wear?
Does Android Wear support direct access to the Internet?
So, for Android Wear 2.0+ Volley requests from wearable app should work.
If you want to use Android Wear <2.0, then:
On Wearable, in onCreate() add a key that indicates whether the phone should start collecting data.
PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/shouldStart");
putDataMapReq.getDataMap().putBoolean(SHOULD_START_KEY, true);
PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
PendingResult pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq);
On phone, in onDataChanged, check if wearable wants to start collecting data. If yes, start Volley request.
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
// DataItem changed
DataItem item = event.getDataItem();
if (item.getUri().getPath().compareTo("/shouldStart") == 0) {
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
boolean shouldStart = dataMap.getBoolean(SHOULD_START_KEY));
if(shouldStart) {
Volley.newRequestQueue(this).add(request);
}
}
} else if (event.getType() == DataEvent.TYPE_DELETED) {
// DataItem deleted
}
}
Then, your Volley request's onResponse should pass data back to Wearable.
public void onResponse(String response) {
PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/data");
putDataMapReq.getDataMap().putString(DATA_KEY, true);
PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
PendingResult pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq);
}
Finally, you can access data in your Wearable using onDataChanged and store it in your model for passing it onto adapter:
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
// DataItem changed
DataItem item = event.getDataItem();
if (item.getUri().getPath().compareTo("/data") == 0) {
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
parseAndpassToAdapter(dataMap.getString(DATA_KEY));
}
} else if (event.getType() == DataEvent.TYPE_DELETED) {
// DataItem deleted
}
}
You'll need Wearable.API to implement this and your class should implement DataApi.DataListener. For more information getting started, refer to Accessing the Wearable Data Layer and Syncing Data Items
Hope this helps.
I am also using volley on an Android wear app I built and I am running it on a Moto 360, I have run into the same problem a couple o times. Try restarting the device. Go to Settings > Restart. It sounds silly but it has worked for me.
You could try an alternative to volley if you can rule out the connection as the problem:
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:support-v4:23.1.0'
compile 'com.android.support:design:23.1.0'
compile 'com.google.code.gson:gson:2.2.4'
compile 'com.google.api-client:google-api-client:1.20.0'
The versions are important.
Then to your request:
Map<String, String> contentParams = new HashMap<>();
InputStream is = null;
NetHttpTransport transport = null;
HttpRequest request = null;
HttpResponse resp = null;
HttpHeaders headers = new HttpHeaders();
JSONObject json = null;
try {
transport = new NetHttpTransport();
HttpRequestFactory factory = transport.createRequestFactory();
request = factory.buildPostRequest(new GenericUrl(url), null);
contentParams = getContentParameters();
headers.putAll(getHeaderParameters());
request.setHeaders(headers);
request.getUrl().putAll(contentParams);
resp = request.execute();
is = resp.getContent();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
string = getJSONFromInputStream(is);
json = new JSONObject(string);
}
} catch (Exception e) {
e.printStackTrace();
}
}
transport.shutdown();
protected Map<String, String> getContentParameters() {
Map<String, String> m = new HashMap<>();
m.put("version", "3.0.5");
m.put("email", userEmail);
m.put("password", userToken);
return m;
}
protected Map<String, String> getHeaderParameters() {
Map<String, String> m = new HashMap<>();
m.put("Referer", "app:/VenstarCloud.swf");
return m;
}
protected String getJSONFromInputStream(InputStream is) {
if (is == null)
throw new NullPointerException();
//instantiates a reader with max size
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8 * 1024);
StringBuilder sb = new StringBuilder();
try {
//reads the response line by line (and separates by a line-break)
String line;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//closes the inputStream
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
Then just execute your code from a thread/asynctask/have it delay your frontend slightly
Edit:
Just in case there is a problem with appending a map:
for (Entry<String, String> entry : getHeaderParameters()) {
headers.put(entry.getKey(), entry.getValue());
}
for (Entry<String, String> entry : getContentParameters()) {
request.getUrl().put(entry.getKey(), entry.getValue());
}
Also as another note, make sure to change the return type from void on both those methods to Map
Is this not just the case of when the watch is connected to the phone via bluetooth the internet will not work, as wifi is turned off. If the watch is using wifi to connect to the phone then it will work.
I'm working on wear 2.0 app and just turn blueooth off on my phone for my watch to get internet connection.
I am writing code for an Android app using Eclipse that is supposed to download an image from a URL (which is generated by the app, elsewhere in the code, using GPS information), then attach the newly downloaded image to an e-mail to be sent. I am able to, in general, accomplish this without much issue.
My problem is this: I only want one image downloaded by the app to be present in the device's external storage at any given time. Deleting the image after the email intent does not work, because because the app doesn't always call onStop or onDestroy when switching to another app to send the email. Time-sensitive deleting of the image will not work either, because I cannot assume that the user will send only one email from the app per hour. I want to give the user the freedom of sending as many of these emails (with one newly downloaded image, each) as they wish.
My current method (which works MOST of the time) is this: in the downloadFile method, simply check for the file's existence (I call it sensorMap.png), then delete it if it exists, before downloading a new one. This SHOULD ensure that there may be only one sensorMap.png image in external storage at any given time (EDIT: it does do this), and that when it comes time to attach the image to the email intent, there will be exactly one image ready to go. Instead, I see that sometimes a second sensorMap image is sometimes being downloaded into storage (i.e. "sensorMap-1.png"), OR the image cannot be attached to the email due to a "File size: 0 bytes" error, OR the image cannot be attached due to a "File does not exist" error. I am unsure what the difference between the latter two problems is. EDIT: Upon manually examining the contents of the directory I created, it seems that, as intended, I end up with only one image titled "sensorMap.png" at a time; it remains in the directory after the app closes, as expected. However, I still occasionally get the "File size: 0 bytes" message or the "File does not exist." message with no attached image, even though I see that the image DOES exist upon looking in directory afterwards. Other times, everything works just fine. It's rather bewildering.
In addition, there is an issue of the button which sends the email becoming unresponsive occasionally. Most of the time, it prompts the user to select an email client, as intended, but occasionally the button will LOOK as if clicked, but do nothing. When this happens, the logcat does not sense that the button was even clicked (I inserted a println statement to test it).
I am unsure of why my delete-before-download is not working flawlessly; the basic idea, at least, appears to be logically sound. Here is the code pertaining to my issue:
Code used to download file (in MainCountActivity.java):
//Function to download image given URL. Will use to attach image file to email.
public void downloadFile(String uRl) {
//delete existing file first so that only one sensorMap image exists in memory
//at any given time.
File file = new File(Environment.getExternalStorageDirectory()+"/SensorLocationImages");
File checkFile = new File(Environment.getExternalStorageDirectory()+"/SensorLocationImages/sensorMap.png");
if(checkFile.exists())
{
//debugging:
System.out.println("About to delete file!");
//deleteFiles(Environment.getExternalStorageDirectory()+"/SensorLocationImages");
checkFile.delete();
}
DownloadManager mgr = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
Uri downloadUri = Uri.parse(uRl);
DownloadManager.Request request = new DownloadManager.Request(
downloadUri);
request.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI
| DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(false).setTitle("Sensor Location Map")
.setDescription("Pinpointed is the location from which the log file was sent.")
.setDestinationInExternalPublicDir("/SensorLocationImages", "sensorMap.png");
mgr.enqueue(request);
}
public Activity getActivity() //I wasn't sure if this would work, but it did. Or at least appears to.
{ return this; }
Method to send email (in MainCountActivity.java):
public void sendEmail(String toAddress, String ccAddress, String bccAddress, String subject, String body, String attachmentMimeType) throws Exception{
try {
Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
emailIntent.setType(attachmentMimeType); //new
String sToAddress[] = { toAddress };
String sCCAddress[] = { ccAddress};
String sBCCAddress[] = { bccAddress };
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_EMAIL, sToAddress);
emailIntent.putExtra(android.content.Intent.EXTRA_CC, sCCAddress);
emailIntent.putExtra(android.content.Intent.EXTRA_BCC, sBCCAddress);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
//get URI of logfile
File tempFile = new File(Environment.getExternalStorageDirectory () + MainCountActivity.dirPath);
Uri uri = Uri.fromFile(tempFile);
//create URI arraylist and add first URI
ArrayList<Uri> uris = new ArrayList<Uri>();
uris.add(uri);
//get URI of map image and add to arraylist
//make sure it is there to attach
File file = new File(Environment.getExternalStorageDirectory()+"/SensorLocationImages");
do {
downloadFile(getMapLink());
//createDirectoryAndSaveFile(getBitmapFromURL(getMapLink()), "sensorMap.png");
} while (!file.exists());
uris.add(Uri.fromFile(new File(Environment
.getExternalStorageDirectory()
+ "/SensorLocationImages/sensorMap.png")));
//+ "/sdcard/SensorLocationImages/sensorMap.png")));
emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
startActivity(emailIntent);
}
catch(Exception ex) {
ex.printStackTrace();
throw ex;
}
}
OnClick method, for my occasional button issue (In MaincountActivity.java):
public void onClick(View v){
switch(v.getId())
{
case R.id.textView1:
{
break;
}
case R.id.Reset:
{
//allowCounting will let the program know when to let it to count or not, depending if Start or Stop button are pressed.
logCount=0;
mCounter.setText("Total: 0");
mToggle.setChecked(false);
break;
}
/* case R.id.toggleButton:
{
break;
}*/
case R.id.SendEmail:
{
//for debugging purposes:
System.out.println("Email button being clicked!");
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))
{
Toast.makeText(this, "GPS is enabled in your device", Toast.LENGTH_SHORT).show();
try {
sendEmail("","","","Sensor Log Info",getEmailBody(),"multipart/mixed");
} catch (Exception e) {
e.printStackTrace();
}
}
else
{
showGPSAlertForEmail();
}
break;
}
}
Basically, I really want to know why my delete-then-download method has not worked every time. Logcat errors have provided no insight. Thank you for your time.