Querying the DownloadManger appears to be the only way to retrieve a file ID and remove its reference in the stock "Downloads" app. This is suggested in Clear out android Downloads list. I have been unable to make this work.
My app is privately distributed and updates itself whenever necessary. That works well, but I want to programmatically delete the update .apk file when the new version starts. The delete succeeds, but the file remains listed in Downloads. If the user taps on it, they receive a "Parse error" because it's obviously no longer there. Ugly...
I'd like to clean this up. Here's the relevant part of my code (which is called before the file is deleted):
public void DoCleanup(Context context) {
DownloadManager oDM = (DownloadManager)context.getSystemService(android.content.Context.DOWNLOAD_SERVICE);
Cursor oCur = oDM.query(new DownloadManager.Query().setFilterByStatus(o_DM.STATUS_SUCCESSFUL));
if (oCur.getCount() > 0) {
oCur.moveToPosition(-1);
while (oCur.moveToNext()) {
if ( --some condition-- ) {
int iRemove = oDM.remove(oCur.getLong(oCur.getColumnIndex(oDM.COLUMN_ID)));
}
}
}
}
oCur.getCount() is always 0 even if no filter is set on the query object. Why? The file is definitely in the folder obtained from Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).
Related
I have an android app. I have a few users who have a recurring problem: When the app shuts down, every file the app saved is gone. Every folder created is gone. Everything is completely wiped back to square one.
I am carefully saving the game data during every transition and game event, so I am very confident that this is not a case of the user crashing out before the data can be written. Somehow, the data that IS being written but then it's just not persisting after the app is removed from memory.
So-- has anyone had this situation and solved it? The only thing I can imagine is that there's some kind of "filesystem.commit" command I need to call after writing the files, but I can't find that documented anywhere.
Please help!
(Edit) I'm using native code to read and write files. The code I use to write a file is this:
bool WriteFile(char *theFilename, char *theDataPtr, int theLen)
{
FILE* aFile=fopen(theFilename,"w+");
if(!aFile) {Alert("unable to create file %s with error %d", theFilename, errno);return false;}
if(aFile) fclose(aFile);
aFile=fopen(theFilename,"w+b");
if(!aFile) {Alert ("unable to open file %s", theFilename);return false;}
if (aFile)
{
fwrite(theDataPtr, 1, theLen,aFile);
fclose(aFile);
return true;
}
return false;
}
Note:No customers are reporting any alert popups, which are just normal Android message boxes. Also note that this code works on almost every other system-- there's just a few customers that get the wiped data, so I was wondering if it's some weird security or some extra step I need to do to be 100% compatible with all systems.
(Edit) One more piece of information... this is the Java code I use to get the storage path for the app... all files that I try to write are put in this folder.
private void SetFilePath()
{
String storagePath = getFilesDir().getAbsolutePath();
// SDCARD
try {
String storageState = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(storageState))
storagePath = getExternalFilesDir(null).getAbsolutePath();
} catch (Exception e) {
Log.v(IDS.LOG,
"No permission to access external storage, missing android.permission.WRITE_EXTERNAL_STORAGE");
}
SetFilePathNative(storagePath); // Tells the native code the path
mStorageDir = storagePath;
}
I'm having a problem starting an activity in a downloaded feature module when it's published to the play store. It always crashes on setContentView() in the downloaded modules activity.
java.lang.RuntimeException: Unable to start activity ComponentInfo{xxx/xxxActivity}: android.content.res.Resources$NotFoundException: Resource ID #0x7e080000
Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x7e080000
at android.content.res.ResourcesImpl.getValue(ResourcesImpl.java:227)
at android.content.res.Resources.loadXmlResourceParser(Resources.java:2149)
at android.content.res.Resources.getLayout(Resources.java:1158)
at android.view.LayoutInflater.inflate(LayoutInflater.java:421)
at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:469)
at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:140)
The really strange part is that if I publish a new version of the app (only change is versionCode) to play store and update the app everything works perfectly.
When I uninstall the app and install it again the crash returns.
my Application is inheriting SplitCompatApplication() and just to be sure I've since tried to add:
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
SplitCompat.install(this)
}
to the activty in the feature module and disabled proguard to make sure nothing is removed during minify
My SplitInstallStateUpdatedListener
private val listener = SplitInstallStateUpdatedListener { state ->
val multiInstall = state.moduleNames().size > 1
state.moduleNames().forEach { name ->
// Handle changes in state.
when (state.status()) {
SplitInstallSessionStatus.DOWNLOADING -> {
// In order to see this, the application has to be uploaded to the Play Store.
displayLoadingState(state, "Laddar ner $name")
}
SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> {
/*
This may occur when attempting to download a sufficiently large module.
In order to see this, the application has to be uploaded to the Play Store.
Then features can be requested until the confirmation path is triggered.
*/
startIntentSender(state.resolutionIntent()?.intentSender, null, 0, 0, 0)
}
SplitInstallSessionStatus.INSTALLED -> {
if(toInstall.isNotEmpty() && toInstall.contains(name)) {
toInstall.remove(name)
}
if(toInstall.isEmpty()) {
// Updates the app’s context with the code and resources of the
// installed module. (should only be for instant apps but tried it anyway, no change)
SplitInstallHelper.updateAppInfo(applicationContext)
Handler().post {
viewModel.goToOverview()
}
}
}
SplitInstallSessionStatus.INSTALLING -> displayLoadingState(state, "Installerar $name")
SplitInstallSessionStatus.FAILED -> {
toastAndLog("Error: ${state.errorCode()} for module ${state.moduleNames()}")
}
}
}
}
This code downloads modules depending on user claims and starts an activity in the base app
The downloaded modules activity is then started from a BottomSheetDialogFragment like this:
xxx.setOnClickListener(view -> {
Intent intent = new Intent();
String packageName = Constants.MODULE_BASEPACKAGE + "." + Constants.MODULE_XXXXX;
intent.setClassName(getActivity().getPackageName(),packageName + ".XxxxxActivity" );
ParcelUuid parcelUuid = new ParcelUuid(UUID.randomUUID());
intent.putExtra("uuid", parcelUuid);
startActivity(intent);
dismiss();
});
I'm all out of ideas about what to try next. It seems like it's something that doesn't update the resource list until an update is installed and a restart of the app is not enough, or am I just missing something simple?
You can always access the resources from the main project inside the dynamic module, so you could just put your resources for the dynamic module in the main app, and then use the R.java from the main App.
However, the proper way to open these resources is to use SplitCompat.install(this) inside the dynamic delivered activity
This seems to have been a bug in com.android.tools.build:gradle:3.2.1
When I upgraded to 3.3.0 the problem resolved itself.
Hopefully it might help someone else who has this problem...
I had an exactly same problem; fresh install crashes with Resources$NotFoundException, but subsequent upgrade works OK (the dynamic module is not downloaded again). But my case was slightly different, because instead of starting an Activity in the dynamic module, I wanted to load a Fragment through Navigation. In that case, I should have just navigated and let Navigation do its thing without manually checking the module was loaded or not (refer to https://developer.android.com/guide/navigation/navigation-dynamic for more info).
// Just navigate without calling splitInstallManager.installedModules.contains()
findNavController().navigate(DynamicDeliveryDirections.actionDynamicFragment())
If you want to start an Activity, you do need to check whether the module is loaded or not, as you are already doing. I suggest you take a look at Google's example, which does exactly what you are trying to do.
https://codelabs.developers.google.com/codelabs/on-demand-dynamic-delivery/index.html?index=..%2F..index#1
As for my case, I had to make sure the package names were correct. For example, if the main module's package name is com.example.foo and dynamic module is com.example.foo.dynamic_activity, then starting the Activity in the dynamic module would look like the following.
Intent().setClassName(
"com.example.foo",
"com.example.foo.dynamic_activity.DynamicActivity"
).also {
startActivity(it)
}
I don't know why it works, but for me using AppCompatActivity solves this problem
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 have android app which create file and read file on google drive (synchronization sql lite db between devices). Create file is running ok. Read file is ok too but only for second attempt. First time my query returns always 0. It looks like first time query check only local storage?
Example: I create export from mobile. It is ok. I can see that file was created and i see it for example via web on google drive. I can see it also via android drive app. So I can do import from this file from my tablet from my app. First attempt is every time failed. Query could not find the file. Second attempt: File was find and imported. Why is this behaviour?
Query is creating like this:
Query query = new Query.Builder().addFilter(Filters.and(
Filters.eq(SearchableField.MIME_TYPE, "text/xml"),
Filters.eq(SearchableField.TITLE,
getResources().getString(R.string.app_file_name)),
Filters.eq(SearchableField.TRASHED, false))).build();
Drive.DriveApi.query(mGoogleApiClient, query)
.setResultCallback(metadataCallback);
and read file is like this in callback result:
MetadataBuffer mdbf = null;
mdbf = result.getMetadataBuffer();
int iCount = mdbf.getCount();
tvout("file count: "+String.valueOf(iCount));
if (iCount == 1){
myFileId = mdbf.get(0).getDriveId();
Log.i(LOG_TAG, "file was found");
readFile();
}
else
{
displayAlertBox(getApplicationContext().getResources()
.getString(R.string.import_alert_res_not_ok)+" (ER-NOFILE)");
}
I do it like it is implemented in google drive api example - query file.
I did really a lot of tests also with sync function.
But every time my iCount is 0 for the first time. Second time it is 1. File was found.
It's because of, The local sync takes a little bit to process. If you make a request before its done, you may get incomplete results. You can use requestSync to wait for a sync to have completed.
Drive.DriveApi.requestSync(mGoogleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.getStatus().isSuccess()) {
// Do you work here
}
}
});
}
Note that, Don't try sync operation too often.
In order to avoid excessive load on the device and the server, sync
requests are rate limited. In this case, the operation will fail with
DRIVE_RATE_LIMIT_EXCEEDED status, which indicates that a sync
already happened quite recently so there is no need for another sync.
The operation will succeed when reattempted after a sufficient backoff
duration.
I have a Bluetooth application, which will list out all of the files in the bluetooth folder on my phone. So far I have only done it this way:
public class GetCaseInformation {
int numberOfFiles;
String pathToFolder;
File folder;
public GetCaseInformation(String pathToFolder) {
this.pathToFolder = pathToFolder;
folder = new File(pathToFolder);
}
public int getCasesFromFolder() {
return folder.list().length;
}
}
A very simple way to check how many files I have in my folder. The method getCasesFromFolder() is called in my onCreate method, so this will just be updated every time the user goes to the home screen. Is it possible to listen for changes in a spesific folder? So I dynamicaly can update the count of files in this folder?
You can try FileObserver:
Monitors files (using inotify) to fire an event after files are
accessed or changed by by any process on the device (including this
one). FileObserver is an abstract class; subclasses must implement the
event handler onEvent(int, String).