I have an app that makes an http request via the localhost to a separate, third-party app which I do not control, and waits for a response from that call before continuing. The workflow goes like this:
User is inside my app
User presses a button, which launches and calls out to the third-party application
User interacts with the third-party application
When the third-party application finishes its work, my app picks up the completed http response, and pulls itself back to the forefront via MoveTaskToFront for the user to continue working.
This functions properly in Android 9 and below, but the last step does not work in Android 10, I believe due to the new restrictions on launching activities from the background.
I have no control over the third-party app, so I cannot modify it to close itself when finished working, or request that the calling app be returned to the foreground when appropriate. Does anyone know of a workaround for this?
Edit: as requested, I've added the code snippet with the call out. This is a Xamarin project, so it's written in C#, but this particular code section is Android-platform-specific, so I am able to make Android system calls.
First I have to bring up the third-party app:
Intent intent = CrossCurrentActivity.Current.AppContext.PackageManager.GetLaunchIntentForPackage("com.bbpos.android.tsys");
if (intent != null)
{
// We found the activity now start the activity
intent.AddFlags(ActivityFlags.ClearTask);
CrossCurrentActivity.Current.AppContext.StartActivity(intent);
}
Then I call into it via the localhost, process the response, and want to switch back to my app.
using (var client = new HttpClient())
{
// by calling .Result we're forcing synchronicity
var response = client.GetAsync("http://127.0.0.1:8080/v2/pos?TransportKey=" + pTransportKey + "&Format=JSON").Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
// as above, forcing synchronicity
string responseString = responseContent.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<GeniusTransactionResponse>(responseString);
var manager = (ActivityManager)Application.Context.GetSystemService(Context.ActivityService);
var test = manager.AppTasks.First().TaskInfo.Id;
manager.AppTasks.First().MoveToFront();
//manager.MoveTaskToFront(CrossCurrentActivity.Current.Activity.TaskId, 0);
return result;
}
else
{
return null;
}
}
Quick update in case anyone else has this same issue: I was able to work around this by adding an Accessibility Service to the project. Simply having an Accessibility Service registered and enabled by the user allows MoveTaskToFront to function as it did in APIs <29; the actual service doesn't need to do anything.
I am trying to show a large list in react native - Expo. When I lock the screen while data loading via API. App State changed from "Active" to "Inactive".
When I return to an active state, no data has been loaded. The App processes are stopped. ListEmptyComponent renders the ActivityIndicator. It is loading indefinitely. It occurs only in android build.
I tried to recall the API by AppState.
const handleAppStateChange = nextAppState => {
console.log(nextAppState);
if (nextAppState === 'active') {
console.log(JSON.stringify(Store.apiCall));
// "Store.apiCall" has data about last API Call and its status.
if (Store.apiCall.status === codes.PENDING || Store.apiCall.status === codes.ERROR) {
api(Store.apiCall.payload);
}
}
setAppState(nextAppState);
};
Still it doesn't works..
This happening because of the battery optimization features of newer Android versions. The only fix would be to ask the user to disable the battery optimization for your app by redirecting them to your app settings or you can use a node package like this one:
https://www.npmjs.com/package/react-native-disable-battery-optimizations
Also, if you dont want your device to sleep if its in active state you can use something like this:
https://www.npmjs.com/package/react-native-keep-awake
Hope this helps :)
I am working on an app with Appcelerator.
Appcelerator uses nearly the same kind of require.js like node.js does.
Now I want to implement a feature that logges out the current user and does not leave any trace.
The most simple way would be to restart the app, but Appcelerator and especially Apple does not support this.
So i have to open the login window and clean all the data that leaves a trace to the old user.
The easiest way would be to dereference one of the main nodes in the require chain leaving all the data dereferenced and garbage collected.
I know there is a way (as mentioned here) to do that in node:
/**
* Removes a module from the cache
*/
function purgeCache(moduleName) {
// Traverse the cache looking for the files
// loaded by the specified module name
searchCache(moduleName, function (mod) {
delete require.cache[mod.id];
});
// Remove cached paths to the module.
// Thanks to #bentael for pointing this out.
Object.keys(module.constructor._pathCache).forEach(function(cacheKey) {
if (cacheKey.indexOf(moduleName)>0) {
delete module.constructor._pathCache[cacheKey];
}
});
};
/**
* Traverses the cache to search for all the cached
* files of the specified module name
*/
function searchCache(moduleName, callback) {
// Resolve the module identified by the specified name
var mod = require.resolve(moduleName);
// Check if the module has been resolved and found within
// the cache
if (mod && ((mod = require.cache[mod]) !== undefined)) {
// Recursively go over the results
(function traverse(mod) {
// Go over each of the module's children and
// traverse them
mod.children.forEach(function (child) {
traverse(child);
});
// Call the specified callback providing the
// found cached module
callback(mod);
}(mod));
}
};
So I tried to read out the require-cache in Appcelerator with:console.log(require, "-" ,require.cache); with an output like: <KrollCallback: 0x79f6fe50> - <null>
So now my questions:
Is there a way to reach the require-cache in Appcelerator?
Do you know a way to clean up a big Appcelerator-App?
Since it is possible to wirte native Modules for Appcelerator:
Do you know a way to clean up a big Android App?
Do you know a way to clean up a big iOS App?
Thank you very much
I've been wondering where (in Android/ iOS) the cookie received from an XMLHttpRequest gets stored...
The situation:
I perform an XHR-request to authenticate. For some reason this starts a kind of a session and all other requests I perform do not need credentials anymore. This situation is wanted, but there is a section in the application where other credentials are needed. When I perform another XHR-request, it does not matter which credentials I use, it will keep using the credentials I entered at first.
What I use:
jQueryMobile
Angular
What I noticed [ANDROID]:
The credentials or the session gets killed on app restart!
(NOT WHEN IN BACKGROUND - Like when backbutton is pressed - IT NEEDS TO BE CLOSED COMPLETELY). So then I have to login again.
What I tried without success:
Adding a param to the URL when I want new credentials to be used.
var xhr = new XMLHttpRequest;
xhr.open('GET', 'test.html?_=' + new Date().getTime());
xhr.send();
The InAppBrowser functionality:
window.open("index.html", "_self",
"location=no,clearsessioncache=yes");
Deleted the applicationCache from the Cordova File-plugin.
A plugin which I thought would help: https://github.com/bez4pieci/Phonegap-Cookies-Plugin
What I want:
I have no clue where to find that session anymore... I really want to know where it's stored. I need a way to delete/ clear it so I can perform another succesful call with other XMLHttpCredentials.
Remember I still want to use the cache functionality the XMLHttpRequest automatically provides.
Any help from experts would be appreciated.
As far as I know, the only way to clear these credentials on Android is by restarting the app. It's possible to do this programmatically if you really need to.
On iOS you can manage saved credentials in the sharedCredentialStorage. For example, to remove all credentials:
NSDictionary* credentialsDict = [[NSURLCredentialStorage sharedCredentialStorage] allCredentials];
for (NSURLProtectionSpace* protectionSpace in credentialsDict){
NSDictionary* userNameDict = credentialsDict[protectionSpace];
for (NSString* userName in userNameDict){
NSURLCredential* credential = userNameDict[userName];
[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace];
}
}
In order to access the native APIs, you'll have to build a custom plugin.
The solution I found for Android was not to restart the entire app, but to isolate the login activity in a separate process, then override the onDestroy() method by adding Process.killProcess(Process.myPid()).
In manifest:
<activity
android:name=".Activities.AdfsLoginActivity"
android:noHistory="true"
android:excludeFromRecents="true"
android:process=":adfsLoginProcess"
android:windowSoftInputMode="stateHidden|adjustResize" />
In LoginActivity:
#Override
protected void onDestroy() {
super.onDestroy();
//kills current process - adfsLoginProcess
Process.killProcess(Process.myPid());
}
This solution is not perfect (as I am not a fan of Process.killProcess()), but it seems far better than killing the entire app and scheduling a restart. If anyone has a better solution, please share.
Running the code below, I create a folder with Google Drive Android API on a tablet. After a few seconds, delete that folder from a remote location on a PC. When I re-run the code, the API still thinks 'MyFolder' exists, even though it was deleted and not visible in the Google Drive app on the tablet. The folder persistance finally disappears after a while and the code works as expected. Is this expected behavior for Cloud drives?
Query query = new Query.Builder()
.addFilter(Filters.and(Filters.eq(
SearchableField.TITLE, "MyFolder"),
Filters.eq(SearchableField.TRASHED, false)))
.build();
Drive.DriveApi.query(getGoogleApiClient(), query)
.setResultCallback(new ResultCallback<DriveApi.MetadataBufferResult>() {
#Override
public void onResult(DriveApi.MetadataBufferResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Cannot create folder in the root.");
} else {
boolean isFound = false;
for(Metadata m : result.getMetadataBuffer()) {
if(!isFound) {
if (m.getTitle().equals("MyFolder")) {
showMessage("Folder exists");
isFound = true;
}
}
}
if(!isFound) {
showMessage("Folder not found; creating it.");
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("MyFolder")
.build();
Drive.DriveApi.getRootFolder(getGoogleApiClient())
.createFolder(getGoogleApiClient(), changeSet)
.setResultCallback(new ResultCallback<DriveFolder.DriveFolderResult>() {
#Override
public void onResult(DriveFolder.DriveFolderResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create the folder");
} else {
mThwingAlbertFolderId = result.getDriveFolder().getDriveId();
showMessage("Created a folder: " + mThwingAlbertFolderId);
}
}
});
}
}
}
});
What you are seeing, is a 'normal' behavior of the GDAA, that can be explained if you look closer at the 'Lifecycle of a Drive file' diagram (warning: I've never seen the source code, just assuming from what I observed).
See, the GDAA, unlike the REST Api, creates a layer that does its best to create caching and network traffic optimization. So, when you manipulate the file/folder from the 'outside' (like the web app), the GDAA layer has no knowledge of the fact until it initiates synchronization, controlled by it's own logic. I myself originally assumed that GooDrive has this under control by dispatching some kind of notification back to the GDAA, but it apparently is not the case. Also, some Googlers mentioned 'requestSync()' as a cure, but I never succeeded to make it work.
What you think you're doing, is polling the GooDrive. But effectively, you're polling the GDAA (local GooPlaySvcs) whose DriveId is still valid (not updated), unlike the real GooDrive object that is already gone.
This is one thing that is not clearly stated in the docs. GDAA is not the best Api for EVERY application. It's caching mechanism is great for transparently managing online/offline states, network traffic optimization. battery life, ... But in your situation, you may be better off by using the REST Api, since the response you get reflects the current GooDrive state.
I myself faced a similar situation and had to switch from the GDAA back to the REST (and replaced polling with a private GCM based notification system). Needless to say, by using the REST Api, your app gets more complex, usually requiring sync adapter / service to do the data synchronization, managing network states, ... all the stuff GDAA gives you for free).
In case you want to play with the 2 apis side-by side, there are two identical CRUD implementation you can use (GDAA, REST) on Github.
Good Luck
Google drive api does not sync immediately, That is why the deleted folders are still showing, so you have to force google drive to sync using requestSync()
Drive.DriveApi.requestSync(mGoogleApiClient).await();
I fount an example snippet here:
http://wiki.workassis.com/android-google-drive-api-deleted-folder-still-exists-in-query/
As Sean mentioned, the Drive Android API caches metadata locally to reduce bandwidth and battery usage.
When you perform an action on the device, e.g. creating a folder, we attempt to apply that action on the server as soon as possible. Though there can be delays due to action dependencies and content transfers, you will generally see the results reflected on the server very quickly.
When an action is performed on the server, e.g. by deleting a folder via the web client, this action is reflected on the device the next time the Drive Android API syncs. In order to conserve battery and bandwidth, sync frequency depends on how the API is being used as this is a priority for users.
If you need to guarantee that a sync has occurred, you can explicitly request a sync using DriveApi.requestSync() and wait on the result. This is currently rate limited to 1 per minute, which is frequently hit during testing, but should have a much smaller impact on real world usage.
Please let us know on our issue tracker if this sync behavior is causing issues for your use case so we can investigate solutions.
Google drive uses its own lifecycle for Drive api and manage all things in cache that's why if you delete some file or folder and try to access using google drive apis it is still available because it is stored in cache so you need to explicitly call requestSync() method for that then after that cache will be updated and gives you that folder or file not found.
below is code for that:
Drive.DriveApi.requestSync(mGoogleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
Log.e("sync_status", status.toString());
if (status.getStatus().isSuccess()) {
setRootFolderDriveId();
}
}
});
and don't call Drive.DriveApi.requestSync(mGoogleApiClient).await() because your main thread will block so it will crash. use above one and after get successful callback you can do your operation on google drive because it's updated.
You can do it in main thread:
Drive.DriveApi.requestSync(mGoogleApiClient).setResultCallback(new ResultCallback<com.google.android.gms.common.api.Status>() {
#Override
public void onResult(com.google.android.gms.common.api.Status status) {
if (!status.getStatus().isSuccess()) {
Log.e("SYNCING", "ERROR" + status.getStatusMessage());
} else {
Log.e("SYNCING", "SUCCESS");
// execute your code to interact with Google Drive
}
}
});
I was having the same issue and using "Drive.DriveApi.requestSync" did the trick.
Also I suggest taking a look at https://github.com/francescocervone/RxDrive because you can concatenate the sync to other drive operations using rxandroid.
For example, this becomes a delete-and-sync operation:
Observable<Boolean> deleteFile = rxDrive.delete(file);
Observable<Void> syncDrive = rxDrive.sync();
Observable.concat(deleteFile, syncDrive);
The reason why you get listed deleted files from your query is that Google Drive has a "Trash" folder that is "searchable". You need to empty your trash first.