How to download files in background regardless of Application state? - java

This has been asked a couple of times on Unity Questions, but never answered.
All I need to do is creare an Android pluugin which downloads few files from given urls and show a downloading progress in notification panel. Downloading should continue even if my Unity application is out of focus.
(source: cuelogic.com)
Here is a peice of code that I have right now:
void DownloadFiles(string[] urls)
{
foreach(var url in urls)
{
StartCoroutine(DownloadFile_CR(url));
}
}
IEnumerator DownloadFile_CR(string url)
{
WWW www = new WWW(url);
while(!www.isDone)
{
yield return null;
}
if(www.error == null)
{
//file downloaded. do something...
}
}
These are some texture files. So How do I get the texture result back from native android code?
Any king of help is appreciated.

I had the same problem. At first, I used a service that worked in the background and downloaded the files I needed, including calculating progress and on complete events.
Then, I made my plugin a little more simple and easy to use. You make an instance of a Java object, providing it with the GameObject name and method name for the responses. I used json to serialize and deserialize java and C# objects, because only strings can be passed between Unity's MonoBehaviour objects and java objects.
Here is how the downnload looks in the android plugin:
Uri Download_Uri = Uri.parse(url);
DownloadManager.Request request = new DownloadManager.Request(Download_Uri);
//Restrict the types of networks over which this download may proceed.
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);
//Set whether this download may proceed over a roaming connection.
request.setAllowedOverRoaming(true);
//Set the local destination for the downloaded file to a path within the application's external files directory
String[] split = url.split("/");
request.setDestinationInExternalFilesDir(activity, null, split[split.length-1]);
//Set the title of this download, to be displayed in notifications (if enabled).
request.setTitle("Downloading " + title);
//Set a description of this download, to be displayed in notifications (if enabled)
request.setDescription("Downloading " + name);
request.setVisibleInDownloadsUi(false);
//Enqueue a new download and get the reference Id
long downloadReference = downloadManager.enqueue(request);
Then you could send back to unity the reference Id so you can get the progress and check if a file is still downloading once your app is been restarted (use SharedPreferences \ PlayerPrefs to store them)

If you want it to continue even when unity is not in focus then you cannot do it in C# in Unity with the WWW class.
If i wanted to do this i would probably write a native Android plugin that starts a download service.
From the official google docs:
A Service is an application component that can perform long-running
operations in the background, and it does not provide a user
interface. Another application component can start a service, and it
continues to run in the background even if the user switches to
another application.
Services are not that complex, you start them with Intents just as you would an activity and there are lots of examples online for this type of service.
Here is the official Android documentation regarding services: https://developer.android.com/guide/components/services.html

Related

Google Drive setup push notifications

We want to receive notifications from google when we do anychange(Add,Edit OR Delete) on google drive folder for these purpose we have integrated google watch api in our spring boot application.
code snippet :
public Channel setUpWatch() throws IOException, GeneralSecurityException {
Channel channel = new Channel();
channel.setAddress("https://somedomain.com/notifications");
channel.setType("web_hook");
channel.setId(UUID.randomUUID().toString());
channel.setKind("api#channel");
StartPageToken pageToken =
driveServiceProvider.getDriveService().changes().getStartPageToken().execute();
System.out.println(pageToken.getStartPageToken());
Channel changesChannel = driveServiceProvider.getDriveService().changes()
.watch(pageToken.getStartPageToken(), channel).execute();
System.out.println(changesChannel.getExpiration());return
return changesChannel;
}
After running this code we are getting 200 in response, but we are not receiving any push notification from google when we do any operation in google drive. We are getting empty changeList
We are checking this code on local with no domain registration
Is there any other way to get the edit, add or deleted files list from google drive?
We do not want any notification on any domain or address. Whenever we trigger the api and if any file(s) is changed, we want that file(s) details like drive url, file name.
It seems that you are passing the current token to the changeList api.
This will return no results.
Actually, the solution is to find the changes since last token till the current token using a loop. So, store the last token somewhere and iterate till last token to current and pass the iterative value to chageList api to get the files changed.
Hope that works.
Push-notification channel will not send you requests for files inside a folder inside of a folder, just because you set up a watch on the folder itself.
To request push notifications, you need to set up a notification channel for each resource you want to watch.
If you preform a watch on a folder you will get a notification if for example the name of the folder is changed.
If you want to know if there are changes to a file then you will need to set up the watch for each of the files.

Google Maps Elevation API android studio

I am creating an android app to record a user's activity using Google Maps SDK and the Google Play Services Location API. I am attempting to retrieve the user's elevation based on a given latitude and longitude. I originally used Location#getAltitude() but then realised that does not give the elevation above sea level.
I proceeded to use the open elevation API using the following query string:
String url = "https://api.open-elevation.com/api/v1/lookup?locations=" + latLng.latitude + "," + latLng.longitude;
However, that API appears to be much too slow in generating a response. I then found the Google Maps Elevation API which we can make a request using a URL also. However, we need to pass an API key and I do not want to pass this API key in the URL string and end up committing it to the remote repository.
In this repo (https://github.com/googlemaps/google-maps-services-java) I found the class:
/src/main/java/com/google/maps/ElevationApi.java which I thought I could use to avoid messing around with http requests.
In my gradle, I included this dependency:
implementation 'com.google.maps:google-maps-services:0.18.0'
At the moment, the code to retrieve the elevation is as follows:
ElevationApi.getByPoint(new GeoApiContext.Builder().apiKey(API_KEY).build(), latLng)
.setCallback(new PendingResult.Callback<ElevationResult>() {
#Override
public void onResult(ElevationResult result) {
consumer.doAction(result.elevation);
}
#Override
public void onFailure(Throwable e) {
e.printStackTrace();
}
});
What do I pass in for API_KEY here since I don't want to commit it to the repository? I have an api key defined in local.properties for maps, however, like so:
MAPS_API_KEY=<API_KEY_HERE>
Basically, my question is, can I define an API key in a properties file that is not committed to GitHub and then reference it in the code?
Thanks for any help.
Update:
I have managed to read the API key from local.properties using gradle but got an exception from the ElevationApi saying API 21+ expected, but was 30...strange. So I went back to the open-elevation API with the following Volley request:
/**
* Calculates elevation gain for the provided recording service
* #param recordingService the recording service to calculate elevation gain for
* #param response the handler to consume the elevation gain with
*/
public static void calculateElevationGain(RecordingService recordingService, ActionHandlerConsumer<Double> response) {
ArrayList<Location> locations = recordingService.getLocations();
JSONArray array = constructLocations(locations);
try {
if (array != null) {
RequestQueue requestQueue = Volley.newRequestQueue(recordingService);
String url = "https://api.open-elevation.com/api/v1/lookup";
JSONObject requestHeader = new JSONObject(); // TODO this seems very slow
requestHeader.put("locations", array);
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, url, requestHeader,
response1 -> handleSuccessfulResponse(response1, response), RecordingUtils::handleErrorResponse);
request.setRetryPolicy(new DefaultRetryPolicy(500000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add(request);
}
} catch (JSONException ex) {
ex.printStackTrace();
}
}
I had to set the timeout to a high number not sure how hight it should be because I was getting Volley timeout errors due to the slow response times.
Are there any other ways I can retrieve elevation about sea level?
Yeah, open-elevation.com has intermittent issues with timeouts and latency.
There are some alternatives listed on this GIS stack exchange question Seeking alternative to Google Maps Elevation API. I'm the developer of Open Topo Data which is the most-voted answer over there. You can host your own server with docker, and I also have a free public API which has pretty good latency and uptime.
There's also GPXZ as an alternative to the Google Elevation API with higher-quality data, but it requires an API key so would have the same issue as with Google Maps.
I advise a different direction: stay with the Google and the API key, but employ best practices regarding secrets and source repositories. Since you are dealing with an Android app and not a webapp your key can be somewhat safe inside your app binary (versus a key in a web deployed app is exposed).
Bets practices:
Do not commit the API key. The best to achieve this is to exclude the file which contains the key from the source control repo. That can simply be done with .gitignore. For example this Codelab has a file with the secret, but it has a dummy value and normally this file should be excluded from the source. It is only there because that is an educational code lab.
As a security measure take advantage of GitGuardian to scan your repos in case you'd accidentally push an API key. In such events you'd get a notification. As for me I forked that Geospatial API codelab and saw the key file was in the gitignore and I accidentally pushed a key.
In case you accidentally push a key in a commit it's not enough to reverse the commit and delete the file! Scavenger bots will still find the information in your git history. Rather immediately disable the key and generate another one.
If you are dealing with a webapp you can restrict the API key usage to your webapp's domain. Similarly you can restrict the key to specific Android app signatures (don't forget to add your developer environment's signature) too. This guarantees that even if someone steals the key they probably won't be able to use it.

codename one local notifications not working on ios

I have added a local notifications so when my app gets a push while opening there is still a popup and a sound.
It's working fine on Android, but on iOS the local notification doesn't appear at all.
The push notifications are working fine on both platforms.
This is my code in the push callback that should trigger the notification (if the app is open):
if(Display.getInstance().getCurrent() != null) {
LocalNotification n = new LocalNotification();
n.setId(value);
n.setAlertBody(value);
n.setAlertTitle({app name});
n.setBadgeNumber(1);
Display.getInstance().scheduleLocalNotification(n, System.currentTimeMillis() + 1000, LocalNotification.REPEAT_NONE);
}
Local notifications don't fire while the app is open in the foreground. You should use a different mechanism to make a sound while the app is running. Eg Display.vibrate()
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"DriverNotification" object:nil userInfo:userInfo];
// [[NSNotificationCenter defaultCenter] postNotificationName:#"UserNotification" object:nil userInfo:userInfo];
NSLog(#"%#",userInfo);
}
Put This Code in Your View Controller
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"DriverNotification" object:nil
];
Did you call registerUserNotificationSettings to register the fact that your app uses local notifications? If you don't do that, your request to post a local notification will be ignored.
See this text from the description of that method:
If your app displays alerts, play sounds, or badges its icon, you must
call this method during your launch cycle to request permission to
alert the user in these ways. (You must also make this request if you
want to set the applicationIconBadgeNumber property directly.)
Typically, you make this request if your app uses local or remote
notifications to alert the user to new information involving your app.
The first time your app launches and calls this method, the system
asks the user whether your app should be allowed to deliver
notifications and stores the response. Thereafter, the system uses the
stored response to determine the actual types of notifications you may
use.
After calling this method, the app calls the
application:didRegisterUserNotificationSettings: method of its app
delegate to report the results. You can use that method to determine
if your request was granted or denied by the user.
It is recommended that you call this method before you schedule any
local notifications or register with the push notification service.
Calling this method with a new user settings object replaces the
previous settings request. Apps that support custom actions must
include all of their supported actions in the notificationSettings
object.
you need to add below code in didFinishLaunchingWithOptions method of AppDelegate.m file for register local notification
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([[UIApplication sharedApplication] respondsToSelector:#selector(registerUserNotificationSettings:)])
{
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
else
{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)];
}
}

Cordova - Retrieve local storage from Push Plugin java files

I am programming a mobile app using Cordova. I am implementing the push notifications using Push Plugin. The app is meant to run on all platforms but right now I am testing on Android and Windows.
In a particular javascript file I am saving a value call it 'category' in the localstorage:
localStorage.setItem("category", JSON.stringify(categoryarray));
Now when sending a push notification, the category is essential to decide whether to show the notification or not. If a user is subscribed to that particular category, then, the notification is to be shown, otherwise not. For this I simply create a condition and check whether the user has subscribed to the category included in the notification (but this is not really relevant to the point of the question). When the app is running this condition can be handled in javascript. When the app is not running, this is handled in java code:
else {
extras.putBoolean("foreground", false);
// Send a notification if there is a message
if (extras.getString("message") != null && extras.getString("message").length() != 0) {
createNotification(context, extras);
}
}
Now I want to get the value from the local storage at that instance that the notification is being pushed when the app is not running (and be able to check whether the notification should be shown or not).
I came into this link: Android Service reads localStorage?
But it seems to be meant for Android native code (reference to the webview). Apart from that I haven't really understood how it works and furthermore if it is applicable for my problem.
What do you suggest? How can I do it?
Edit: I didn't initially realise that the Push plugin java code won't be compiled with the Cordova app. So editing the code that is retrievable from the Cordova directory is in reality useless. Unless, someone can still suggest something, I know that this is an unanswerable question. Will have to re-attempt to create an API for this purpose and handle who to receive which notification at server side! (The reason why I resorted to this method was because I wasn't managing to create an API for notification purposes)
I didn't initially realise that the Push plugin java code won't be compiled with the Cordova app. So editing the code that is retrievable from the Cordova directory is in reality useless. Unless, someone can still suggest something, I know that this is an unanswerable question.
This isn't true, Cordova plugin code is compiled when you compile your Cordova app. All Cordova plugin's provide native source code that gets compiled into the app when you run cordova build (or cordova run <platform>).
If you wanted to solve this completely on the client side (rather than managing the categories that a user is subscribed to on the backend and only sending a notification if the user is subscribed to a category), you could extend the PushPlugin to manage subscriptions to categories.
As a rough sketch:
In PushNotification.js, add a method to subscribe to a channel:
PushNotification.prototype.subscribeToChannel(successCallback, errorCallback, channel) {
cordova.exec(successCallback, errorCallback, "PushPlugin", "subscribeToChannel", [{channel: channel}]);
}
In PushPlugin.java catch the subscribeToChannel action in the execute function:
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) {
...
if ("subscribeToChannel".equals(action)) {
//get the attached data
JSONObject jo = data.getJSONObject(0);
String channel = (String) jo.get("channel");
addChannelToSubscriptions(channel);
}
...
}
public void addChannelToSubscriptions(String channel) {
//store as a list in a sharedpreferences list
}
Then when a notification is received, you can check if the channel is a channel that has been subscribed to.
// Send a notification if subscribed to the channel
if (extras.getString("channel") != null && isSubscribedTo(extras.getString("channel"))) {
createNotification(context, extras);
}
public boolean isSubscribedTo(String channel) {
//see if the channel is in the shared preferences.
}
Personally, I think it'd be easier to manage subscriptions on the backend as to manage it in the app, you'd have to implement this logic for each platform you support. It would be easier to just add a webservice call in your Javascript. As a further alternative, if you don't want to handle the subscription logic on your backend, you could look at a service like Parse where the concept of subscribing to channels is built into the service.

Images not displaying after ajax function until workspace refresh

Background: I just finished a registration form for my site on my local host. Within the form users upload their first profile picture. The form is submitted with ajax, validated on the server side, and the image is written on the server side to a folder. Once the image is written I return that users username to the ajax success and then they are redirected to their newly created homepage.
Problem: When I test the form out on my local host all their data is imported into the db as expected. However the files are written to a folder within my eclipse workspace and it is not noticing the new data. I've tweaked my eclipse workspace preferences but it's refusing to refresh when the new images hit the files. So unfortunately, when the user is redirected to their homepage they are shown a 404 where their image should be. It will stay like this until I go into eclipse and refresh my workspace and then it shows up. I've figured out that the images are completely written to the file before the ajax success is called so the function isn't going too fast, the server (or eclipse workspace) is not refreshing.
Question: Is this something I should worry about when I transfer my site to a godaddy VPS? I am concerned that when users upload images the same thing will happen and they'll have to wait for however long it takes the server to realize there is new content. And this could be devastating to site popularity if I am displaying 404 images to users who are having their first experirience with the site. So Is there something I should do to prevent this problem on my local host. But more importantly do you think this will happen on a live server and if so what should I do?
Code: Here's my javascript code. So should I do anything in my success area to maybe check for this problem?
function addNewUser()
{
var form = new FormData();
var ajaxObject = getAjaxObject();
var gender = (getElement('registerMale').checked) ? "his" : "her";
form.append("userName", getValue('registerUserName'));
form.append("email", getValue('registerEmail'));
form.append("password", getValue('registerPassword'));
form.append("fName", getValue('registerFName'));
form.append("lName", getValue('registerLName'));
form.append("displayName", getValue('registerFName') + " " + getValue("registerLName"));
form.append("location", getValue('registerLocation'));
form.append("gender", gender);
form.append("currentDefault", getElement("registerCurrentDefault").files[0]);
form.append("discipline", getValue("registerDiscipline"));
form.append("birthDay", getValue("registerBirthDay"));
form.append("birthMonth", getValue("registerBirthMonth"));
form.append("birthYear", getValue("registerBirthYear"));
ajaxObject.open("POST", "addNewUser", true);
ajaxObject.send(form);
ajaxObject.onreadystatechange = function()
{
if(ajaxObject.readyState == 4 && ajaxObject.status == 200)
{
divLink("profile?user=" + ajaxObject.responseText);
}
};
}
This is not a problem to worry about.
Try running the app from the application server you are using. You will get the expected result.

Categories