How can I clear the Android app cache? - android

I am writing a app which can programatically clear application cache of all the third party apps installed on the device. Following is the code snippet for Android 2.2
public static void trimCache(Context myAppctx) {
Context context = myAppctx.createPackageContext("com.thirdparty.game",
Context.CONTEXT_INCLUDE_CO|Context.CONTEXT_IGNORE_SECURITY);
File cachDir = context.getCacheDir();
Log.v("Trim", "dir " + cachDir.getPath());
if (cachDir!= null && cachDir.isDirectory()) {
Log.v("Trim", "can read " + cachDir.canRead());
String[] fileNames = cachDir.list();
//Iterate for the fileName and delete
}
}
My manifest has following permissions:
android.permission.CLEAR_APP_CACHE
android.permission.DELETE_CACHE_FILES
Now the problem is that the name of the cache directory is printed but the list of files cachDir.list() always returns null. I am not able to delete the cache directory since the file list is always null.
Is there any other way to clear the application cache?

"android.permission.CLEAR_APP_CACHE" android.permission.DELETE_CACHE_FILES"
Ordinary SDK applications cannot hold the DELETE_CACHE_FILES permission. While you can hold CLEAR_APP_CACHE, there is nothing in the Android SDK that allows you to clear an app's cache.
Is there any other way to clear the application cache?
You are welcome to clear your own cache by deleting the files in that cache.

Check out android.content.pm.PackageManager.clearApplicationUserData: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/content/pm/PackageManager.java/
The other hidden methods in that class might be useful, too.
In case you've never used hidden methods before, you can access hidden methods using Java reflection.

poate iti merge asta
static int clearCacheFolder(final File dir, final int numDays) {
int deletedFiles = 0;
if (dir!= null && dir.isDirectory()) {
try {
for (File child:dir.listFiles()) {
//first delete subdirectories recursively
if (child.isDirectory()) {
deletedFiles += clearCacheFolder(child, numDays);
}
//then delete the files and subdirectories in this dir
//only empty directories can be deleted, so subdirs have been done first
if (child.lastModified() < new Date().getTime() - numDays * DateUtils.DAY_IN_MILLIS) {
if (child.delete()) {
deletedFiles++;
}
}
}
}
catch(Exception e) {
Log.e("ATTENTION!", String.format("Failed to clean the cache, error %s", e.getMessage()));
}
}
return deletedFiles;
}
public static void clearCache(final Context context, final int numDays) {
Log.i("ADVL", String.format("Starting cache prune, deleting files older than %d days", numDays));
int numDeletedFiles = clearCacheFolder(context.getCacheDir(), numDays);
Log.i("ADVL", String.format("Cache pruning completed, %d files deleted", numDeletedFiles));
}

I'm not sure how appropriate this is in terms of convention, but this works so far for me in my Global Application class:
File[] files = cacheDir.listFiles();
for (File file : files){
file.delete();
}
Of course, this doesn't address nested directories, which might be done with a recursive function like this (not tested extensively with subdirectories):
deleteFiles(cacheDir);
private void deleteFiles(File dir){
if (dir != null){
if (dir.listFiles() != null && dir.listFiles().length > 0){
// RECURSIVELY DELETE FILES IN DIRECTORY
for (File file : dir.listFiles()){
deleteFiles(file);
}
} else {
// JUST DELETE FILE
dir.delete();
}
}
}
I didn't use File.isDirectory because it was unreliable in my testing.

Related

Android 11 + Kotlin: Reading a .zip File

I've got an Android app written in Kotlin targeting framework 30+, so I'm working within the new Android 11 file access restrictions. The app needs to be able to open an arbitrary .zip file in the shared storage (chosen interactively by the user) then do stuff with the contents of that .zip file.
I'm getting a URI for the .zip file in what I'm led to understand is the canonical way:
val activity = this
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) {
CoroutineScope(Dispatchers.Main).launch {
if(it != null) doStuffWithZip(activity, it)
...
}
}
getContent.launch("application/zip")
My problem is that the Java.util.zip.ZipFile class I'm using only knows how to open a .zip file specified by a String or a File, and I don't have any easy way to get to either of those from a Uri. (I'm guessing that the ZipFile object needs the actual file rather than some kind of stream because it needs to be able to seek...)
The workaround I'm using at present is to turn the Uri into an InputStream, copy the contents to a temp file in private storage, and make a ZipFile instance from that:
private suspend fun <T> withZipFromUri(
context: Context,
uri: Uri, block: suspend (ZipFile) -> T
) : T {
val file = File(context.filesDir, "tempzip.zip")
try {
return withContext(Dispatchers.IO) {
kotlin.runCatching {
context.contentResolver.openInputStream(uri).use { input ->
if (input == null) throw FileNotFoundException("openInputStream failed")
file.outputStream().use { input.copyTo(it) }
}
ZipFile(file, ZipFile.OPEN_READ).use { block.invoke(it) }
}.getOrThrow()
}
} finally {
file.delete()
}
}
Then, I can use it like this:
suspend fun doStuffWithZip(context: Context, uri: Uri) {
withZipFromUri(context, uri) { // it: ZipFile
for (entry in it.entries()) {
dbg("entry: ${entry.name}") // or whatever
}
}
}
This works, and (in my particular case, where the .zip file in question is never more than a couple MB) is reasonably performant.
But, I tend to regard programming by temporary file as the last refuge of the terminally incompetent, thus I can't escape the feeling that I'm missing a trick here. (Admittedly, I am terminally incompetent in the context of Android + Kotlin, but I'd like to learn to not be...)
Any better ideas? Is there a cleaner way to implement this that doesn't involve making an extra copy of the file?
Copying from external source (and risking downvoting to oblivion) and this isn't quite an answer, but too long for a comment
public class ZipFileUnZipExample {
public static void main(String[] args) {
Path source = Paths.get("/home/mkyong/zip/test.zip");
Path target = Paths.get("/home/mkyong/zip/");
try {
unzipFolder(source, target);
System.out.println("Done");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void unzipFolder(Path source, Path target) throws IOException {
// Put the InputStream obtained from Uri here instead of the FileInputStream perhaps?
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(source.toFile()))) {
// list files in zip
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
boolean isDirectory = false;
// example 1.1
// some zip stored files and folders separately
// e.g data/
// data/folder/
// data/folder/file.txt
if (zipEntry.getName().endsWith(File.separator)) {
isDirectory = true;
}
Path newPath = zipSlipProtect(zipEntry, target);
if (isDirectory) {
Files.createDirectories(newPath);
} else {
// example 1.2
// some zip stored file path only, need create parent directories
// e.g data/folder/file.txt
if (newPath.getParent() != null) {
if (Files.notExists(newPath.getParent())) {
Files.createDirectories(newPath.getParent());
}
}
// copy files, nio
Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING);
// copy files, classic
/*try (FileOutputStream fos = new FileOutputStream(newPath.toFile())) {
byte[] buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}*/
}
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
}
}
// protect zip slip attack
public static Path zipSlipProtect(ZipEntry zipEntry, Path targetDir)
throws IOException {
// test zip slip vulnerability
// Path targetDirResolved = targetDir.resolve("../../" + zipEntry.getName());
Path targetDirResolved = targetDir.resolve(zipEntry.getName());
// make sure normalized file still has targetDir as its prefix
// else throws exception
Path normalizePath = targetDirResolved.normalize();
if (!normalizePath.startsWith(targetDir)) {
throw new IOException("Bad zip entry: " + zipEntry.getName());
}
return normalizePath;
}
}
This apparently works with pre-existing files; however since you already have an InputStream read from the Uri - you can adapt this and give it a shot.
EDIT:
It seems like it's extracting to Files as well - you could store the individual ByteArrays somewhere then decide what to do with them later on. But I hope you get the general idea - you can do all of this in-memory, without having to use the disk (temp files or files) in between.
Your requirement is a bit vague and unclear however, so I don't know what you're trying to do, merely suggesting a venue/approach to try out
What about a simple ZipInputStream ? –
Shark
Good idea #Shark.
InputSteam is = getContentResolver().openInputStream(uri);
ZipInputStream zis = new ZipInputStream(is);
#Shark has it with ZipInputStream. I'm not sure how I missed that to begin with, but I sure did.
My withZipFromUri() method is much simpler and nicer now:
suspend fun <T> withZipFromUri(
context: Context,
uri: Uri, block: suspend (ZipInputStream) -> T
) : T =
withContext(Dispatchers.IO) {
kotlin.runCatching {
context.contentResolver.openInputStream(uri).use { input ->
if (input == null) throw FileNotFoundException("openInputStream failed")
ZipInputStream(input).use {
block.invoke(it)
}
}
}.getOrThrow()
}
This isn't call-compatible with the old one (since the block function now takes a ZipInputStream as a parameter rather than a ZipFile). In my particular case -- and really, in any case where the consumer doesn't mind dealing with entries in the order they appear -- that's OK.
Okio (3-Alpha) has a ZipFileSystem https://github.com/square/okio/blob/master/okio/src/jvmMain/kotlin/okio/ZipFileSystem.kt
You could probably combine it with a custom FileSystem that reads the content of that file. It will require a fair bit of code but will be efficient.
This is an example of a custom filesystem https://github.com/square/okio/blob/88fa50645946bc42725d2f33e143628e7892be1b/okio/src/jvmMain/kotlin/okio/internal/ResourceFileSystem.kt
But I suspect it's simpler to convert the URI to a file and avoid any copying or additional code.
It's easy to check the .zip and .rar files in the Android-Kotlin FileAdapter(work with file manager), add the bellow function to your code:
private fun isZip(name: String): Boolean {
return name.contains(".zip") || name.contains(".rar")
}

NullPointerException: Attempt to get length of null array

I am developing an Android App. I have the below code to write a list of all files starting with XLR in a particular folder:
private List<File> getListFiles(File parentDir) {
ArrayList<File> inFiles = new ArrayList<File>();
File[] files = parentDir.listFiles();
for (File file : files) {
try {
if ((file.exists()) && (file != null)) {
if (file.isDirectory()) {
inFiles.addAll(getListFiles(file));
} else {
if (file.getName().startsWith("XLR")) {
inFiles.add(file);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return inFiles;
}
On Android 10 and 11 it seems to be a problem. For Android 10 I have legacy storage enabled and Android 11 I have got access to all files. So the issue is not file access.
The folder itself might not be in my app's folder, hence the need to be able to list out files
I know that there is an extensive page here on NPE's and I have tried my best to follow the advice there (I am checking that files exist and are not null), but still to no avail.
I feel that there's probably something so stupid, that a more experienced programmer would probably pick out in 2 seconds...
This problem would be easily solved just reading the documentation of the actual File package.
Documentation on the method File::listFiles:
An array of abstract pathnames denoting the files and directories in the directory denoted by this abstract pathname. The array will be empty if the directory is empty. Returns null if this abstract pathname does not denote a directory, or if an I/O error occurs.
As you can see, that method can actually return null and not only an empty array, so you should check that the value isn't null before even trying to iterate over it.

listFiles() but ignore a subdirectory in Android

I need to get a list of files but ignore a particular subdirectory. For example here is a sample structure.
Content
->1
-->file_a.mp4
-->file_b.mp4
->2
-->file_c.mp4
-->file_d.mp4
->Bonus
-->1
--->file_e.mp4
--->file_f.mp4
I need to be able to get a list of files/directories that excludes the bonus directory.
I also need to separate list the files for the bonus directory, but I think that can be easily solved by using the normal method.
How do I perform a list files, but ignore a directory?
Here is my sample code that is going to return everything
final List<Boxset> boxsets = getCloudBoxsetsWithTrackData(context);
final File[] boxsetFiles = dir.listFiles();
if (boxsetFiles != null)
{
for (File subDir : boxsetFiles)
{
if (subDir.isDirectory())
{
for (Boxset boxset : boxsets)
{
if (subDir.getName().equalsIgnoreCase(String.valueOf(boxset.persistentId)))
{
DBHandler.getInstance(context).moveBoxsetToDeviceList(boxset);
DownloadLibrarian.getInstance(context).stopDownload(boxset);
}
}
}
}
}
You can make use of FileFilter to obtain a list of sub-directories that doesn't include Bonus
File[] nonBonusDirs = dir.listFiles(new FileFilter() {
#Override public boolean accept(File file) {
return file.isDirectory() && !file.getName().equalsIgnoreCase("bonus");
}
});
You can then obtain a list of all files not in the Bonus directory
List<File> filesNotInBonusDir = new ArrayList<>();
for (File directory : nonBonusDirs) {
filesNotInBonusDir.addAll(Arrays.asList(directory.listFiles()));
}
Though of course these shenanigans are much nicer in Kotlin thanks to flatMap ;)
val filesNotInBonusDir: List<File> = dir.listFiles()
.filter { it.isDirectory && !it.name.equals("bonus", ignoreCase = true) }
.flatMap { it.listFiles().toList() }

Dropbox API Android listing contents of contents from directory

I would like to list the content of the content of a directory from Dropbox using its Android API.
I tried using dirEntry.contents() in a for loop, like this:
Entry rootDirEnt = mApi.metadata(mPath, 1000, null,
true, null);
if (!rootDirEnt.isDir || rootDirEnt.contents == null) {
// It's not a directory, or there's nothing in it
mErrorMsg = "No files available in Dropbox";
return false;
}
for (Entry childDirEnt : rootDirEnt.contents) {
// check if it still exists
if (childDirEnt.isDir && !childDirEnt.isDeleted
&& childDirEnt.contents != null) {
// childDirEnt contents is already null, even though there are files inside this directory
for (Entry fileEnt : childDirEnt.contents) {
// do smth with file
if (isCancelled()) {
return false;
} else {
publishProgress();
}
}
}
}
So I thought of usign mApi.metadata() again with the new path which works, but my question is: Can't I do it without calling medata() for every directory inside root dir ? (maybe using the contents call or smth.. )
You can call metadata again on each directory you find. Generally, Dropbox discourages calling metadata recursively like that unless driven by user action. See https://www.dropbox.com/developers/core/bestpractices.
You might instead use the delta API. The first time you call that, it will return a full list of all the content your app has access to in a user's Dropbox.

Copy the shared preferences XML file from /data on Samsung device failed

There's an exporting feature in my application. It's just a copy operation since all my settings are store in shared preference.
I just copy the xml file from /data/data/package.name/shared_prefs/settings.xml to SD card. It works fine on my HTC desire. However, it might not work on Samsung devices, and i got the following error while I try to copy the file.
I/System.out( 3166): /data/data/package.name/shared_prefs/settings.xml (No such file or directory)
in the directory.
Anyone know how to fix it, or is there another simple way to store the shared preference ?
Thanks.
Never never never never never never never never never hardwire paths.
Unfortunately, there's no getSharedPreferenceDir() anywhere that I can think of. The best solution I can think of will be:
new File(getFilesDir(), "../shared_prefs")
This way if a device manufacturer elects to change partition names, you are covered.
Try this and see if it helps.
CommonsWare's suggestion would a be clever hack, but unfortunately it won't work.
Samsung does not always put the shared_prefs directory in the same parent directory as the getFilesDir().
I'd recommend testing for the existence of (hardcode it, except for package name):
/dbdata/databases/<package_name>/shared_prefs/package.name_preferences.xml and if it exists use it, otherwise fall back to either CommonsWare's suggestion of new File(getFilesDir(), "../shared_prefs") or just /data/data/<package_name>/shared_prefs/package.name_preferences.xml.
A warning though that this method could potentially have problems if a user switched from a Samsung rom to a custom rom without wiping, as the /dbdata/databases file might be unused but still exist.
More details
On some Samsung devices, such as the Galaxy S series running froyo, the setup is this:
/data/data/<package_name>/(lib|files|databases)
Sometimes there's a shared_prefs there too, but it's just Samsung's attempt to confuse you! Don't trust it! (I think it can happen as a left over from a 2.1 upgrade to 2.2, but it might be a left over from users switching roms. I don't really know, I just have both included in my app's bug report interface and sometimes see both files).
And:
/dbdata/databases/<package_name>/shared_prefs
That's the real shared_prefs directory.
However on the Galaxy Tab on Froyo, it's weird. Generally you have: /data/data/<package_name>/(lib|shared_prefs|files|databases)
With no /dbdata/databases/<package_name> directory, but it seems the system apps do have:
/dbdata/databases/<package_name>/yourdatabase.db
And added bonus is that /dbdata/databases/<package_name> is not removed when your app is uninstalled. Good luck using SharedPreferences if the user ever reinstalls your app!
Try using
context.getFilesDir().getParentFile().getAbsolutePath()
Best way to get valid path on all devices - run method Context.getSharedPrefsFile defined as:
/**
* {#hide}
* Return the full path to the shared prefs file for the given prefs group name.
*
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
*/
public abstract File getSharedPrefsFile(String name);
Because of it hidden need use reflection and use fallback on fail:
private File getSharedPrefsFile(String name) {
Context context = ...;
File file = null;
try {
if (Build.VERSION.SDK_INT >= 24) {
try {
Method m = context.getClass().getMethod("getSharedPreferencesPath", new Class[] {String.class});
file = (File)m.invoke(context, new Object[]{name});
} catch (Throwable e) {
Log.w("App TAG", "Failed call getSharedPreferencesPath", e);
}
}
if (file == null) {
Method m = context.getClass().getMethod("getSharedPrefsFile", new Class[] {String.class});
file = (File)m.invoke(context, new Object[]{name});
}
} catch (Throwable e) {
Log.w("App TAG", "Failed call getSharedPrefsFile", e);
file = new File(context.getFilesDir(), "../shared_prefs/" + name + ".xml");
}
return file;
}
On some Samsungs implements like this:
public File getSharedPrefsFile(String paramString) {
return makeFilename(getPreferencesDir(), paramString + ".xml");
}
private File getPreferencesDir() {
synchronized (this.mSync) {
if (this.mPreferencesDir == null) {
this.mPreferencesDir = new File("/dbdata/databases/" + getPackageName() + "/", "shared_prefs");
}
File localFile = this.mPreferencesDir;
return localFile;
}
}
On other Android like this:
public File getSharedPrefsFile(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
}
return mPreferencesDir;
}
}
private File getDataDirFile() {
if (mPackageInfo != null) {
return mPackageInfo.getDataDirFile();
}
throw new RuntimeException("Not supported in system context");
}
After while Google change API for level 24 and later:
https://android.googlesource.com/platform/frameworks/base/+/6a6cdafaec56fcd793214678c7fcc52f0b860cfc%5E%21/core/java/android/app/ContextImpl.java
I've tested in Samsung P1010 with:
//I'm in a IntentService class
File file = this.getDir("shared_prefs", MODE_PRIVATE);
I got:
"/data/data/package.name/app_shared_prefs"
It works fine to me. I can run ffmpeg in this folder.
Look:
Context.getDir
You have to create the shared_prefs directory:
try{
String dir="/data/data/package.name/shared_prefs";
// Create one directory
boolean success = (new File(dir)).mkdirs();
if (success) {
// now copy the file
}
}catch (Exception e){//Catch exception if any
System.err.println("Error: " + e.getMessage());
}
Also... the package of your app is package.name? Make sure you are referring to the right package.

Categories

Resources