From inside my app, I take a picture using my camera. This picture is then loaded into an ImageView using its Uri which would look something like this: content://media/external/images/media/12345. I can get the path of this Uri by querying the MediaStore, it looks something like this: /storage/emulated/0/DCIM/Camera/IMG_12345.jpg.
I now manually delete the image from that path above. But when I reopen the app, the image is still there and accessible under the same Uri. If I query the MediaStore again it gives me a CursorIndexOutOfBoundsException. So the file does not exist to the MediaStore
So what's happening here? If I've deleted the file manually, then it should not be available to the app. Where is this file right now? Is there a better way of deleting images such that they are removed from everywhere?
The MediaStore is an index of available files. Like any index, it needs to be updated when there are changes to the filesystem. And, like a search engine, there are two main ways in which that happens:
Somebody tells the MediaStore to scan something and add it to the index
The MediaStore crawls the fileystem
In your case, deleting the file using a file manager may not have updated MediaStore, in part because there is no documentation on how to update the MediaStore when you delete something. The docs are focused on indexing new files, not de-listing deleted ones.
The reason why the Uri appeared to continue to work is because of your image-loading library (Glide, in your case). Glide has an in-memory cache, and it will use images from cache when possible.
You should read about caching: https://en.wikipedia.org/wiki/Web_cache
When downloading a resource (such as an image) for the first time, the browser (or most apps using a built-in http communication component) save that resource in the so-called cache. When it comes to reading the resource again next time, they find it has been cached locally, so they don't need to load it again over the net.
You don't have full control over the behaviour, but you can serve your resource via HTTP using the Cache-control and Proxy headers: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
If you want to remove that resource from the cache, well, you have to clean your browser cache. Usually, this can be done in the system settings or in the settings of your browser.
Related
I wanted to know the best approach to passing information from an app to another app on the same devices in android.
For example:
I open google apps and I share a document with my App A.
Google App generated an intent and sends a content URI. From my
understanding, the content uri contains information about the file
(filename, file size, mimetype) and the ability to extract the
content which is located in the cache of the google app on the
device.
When App A opens, it reads the content URI. Ideally, it
should be able to extract the information from the content uri and
then render the image. What this means is that App A will display the image shared. In this example, google app shares a docement, and App A wants to open and display the document within it's own app.
The confusing part
From searching the web, it seems that some people actually try to
extract the file path from the content URI. This requires that you
have permission to access another app's cache or storage space
within the device. Let's say this is possible. It also makes some
assumptions that it's possible to extract the file path.
After reading some articles:
https://commonsware.com/blog/2016/03/14/psa-file-scheme-ban-n-developer-preview.html
https://commonsware.com/blog/2014/07/04/uri-not-necessarily-file.html
https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
it seems that, ideally you should never assume that you can extract the file path and that google has made some updates that makes this not possible.
Work around:
Eventhough i'm not able to extract the file path from the
contentUri, I'm able to read the bytes of what the contentUri is
pointing to. So I could save it to a file that is relevant to the
local cache of App A and pass that path along to get render or pass
the bytes back. This refers to App A displaying the content. That is passing the path or bytes and let's make the assumption that it knows how to display it given that information.
Question:
The work around does not seem ideal because technically you are
save the file again on the device. There are two locations with the
same content ( google app storage and App A's storage). You also
have to manage when to delete the App A's file that you created.
This doesn't really seem ideal and was wondering what the best
approach would be? Or is this the expected flow?
Also I don't know
if it's ideal to pass the bytes back up vs. just a file path.
Update
To be more specific, the app i'm creating is a hybrid where i'm using cordova plugin to interact with a web app. The web app has methods to process or display the shared document based on file path. So ideally I want to keep it consistent with just reading the file path so that the other platforms that the web app supports does not break.
Any advice appreciated,
D
Eventhough i'm not able to extract the file path from the contentUri, I'm able to read the bytes of what the contentUri is pointing to.
Correct. This is not significantly different than how you use an HTTPS URL, where you also do not have direct filesystem access to the content (in that case, resident on a different server).
So I could save it to a file that is relevant to the local cache of App A and pass that path along to get render or pass the bytes back.
Or, just consume the bytes. Again, drawing an analogy to an HTTPS URL, there is no requirement to save those bytes to disk to use them.
The work around does not seem ideal because technically you are save the file again on the device. There are two locations with the same content ( google app storage and App A's storage). You also have to manage when to delete the App A's file that you created.
Then do not save the file again on the device, and simply use the stream of bytes. Again, this is not significantly different than using an HTTPS URL.
This doesn't really seem ideal and was wondering what the best approach would be?
Do not write the bytes to disk. Just use them.
So ideally I want to keep it consistent with just reading the file path so that the other platforms that the web app supports does not break.
Your choices are:
Improve the Web app code, such that a local file path is one possible source of the data, or
Suffer the problems with making copies of that data
After all, bear in mind that the Uri you are given via ACTION_SEND does not have to be a content Uri. It could very easily be an http or https Uri.
My Android app creates a folder on the users device's external storage on launch:
File images = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + File.separator + "rapical" + File.separator);
images.mkdirs();
I have around 16 image icons (.png) that I would like to place in this folder the first time the user opens the app.
What is the best practice to do this? Should I place them in the drawable folder initially and then copy them over to the newly created images folder? Not sure what approach to take!
There are two separate issues here:
What to do with images that the user adds to your app?
What to do with your starter images, for your default foods?
Using external storage for the user-added images has some implications:
Your app can survive that image no longer existing, since the user, or other apps, can delete that file at any point
Your user does not mind that the image will get picked up by other apps, such as photo galleries
Unless you specifically want these images to be user-manipulable outside of your app, I suggest that you use internal storage for the "re-sized, compressed and stored" user-supplied images. The original image might be on external storage (I assume that you are using ACTION_GET_CONTENT and/or ACTION_OPEN_DOCUMENT to get the image), but your modified copy would be private to your app.
I sincerely hope that you are using an image-loading library, like Picasso, for loading these images, since they will handle things like background threads and ListView/RecyclerView image recycling and stuff for you.
In that case, what you store in the SQLite database for your default foods needs to be something that the image-loading library can interpret, to bring in the image that you want.
In that case, I would suggest using assets/ to ship the images and file:///android_asset/... values in the database. file:///android_asset/ points to what amounts to assets/ in your project, so if you have assets/chicken_pad_thai.jpg in the project, Picasso (and any decent image-loading library) would be able to interpret file:///android_asset/chicken_pad_thai.jpg) and load the image.
This saves you from making duplicate copies of the images, saving the user disk space and time on first launch. It also means that if you replace the images in your app (e.g., you get a better photo representing chicken pad thai), the user will start seeing the updated image, without you having to do some extra work to realize that you shipped a new image and have to copy that image out to a file somewhere.
Now, suppose that you really do want the user-supplied images to be on external storage. In that case:
Probably rather than the directory that you chose, I would go with getExternalFilesDir() (a method on Context), as on API Level 19+, you do not need WRITE_EXTERNAL_STORAGE as a permission
If you want the user to be able to manipulate the images of the default foods, then copy those out to that location on first run
I have to do some stuff. I'm downloading images using web-service, after successful download of each image, I'm using "AES" encryption algorithm to encrypt that images. I'have done encryption successfully. But whenever I'm going to open Gallery of device, I am able to seen that downloaded images encrypted by me. I don't want that. I wanna secure that images that can prevent access from Gallery.
I understood the whole process here.When I am storing each image after download Gallery capture that image and store into its cache so before my encryption process may be Gallery populate that images.
-> My Encryption process : Download Image - > Store into SDCARD - > Encrypting Image - > Delete the real Image
But this is not deleting from Gallery also.
So in short I wanna secure that images that can prevent access from Gallery. Let me know your best suggestion about my process is right or wrong? May I have to do a few changes on it? or have you nice idea instead of my process ?
Thanks for your best suggestions.
You must add an empty file to your images folder called .nomedia. This will prevent the MediaScanner from indexes images in that folder.
However, there is a bug on ICS that makes this slightly harder. On ICS, your folder must have the .nomedia present before you add images to it, or else the images will be indexed.
Another, slightly less compliant option is to begin your folder name with a dot (something like .foldername). As Android is UNIX based, this will make it a hidden folder, and hence not have the MediaScanner scan it.
I have got a proper solution. That will also work with higher versions.
You can keep it private with your application's private folder using following directory :
String mDirectory = getPackageManager().getPackageInfo("com.example.myapp", 0).applicationInfo.dataDir;
You can store your private data into this package. It will prevent access as well as prevent indexing from Gallery. So you don't need to worry about it's encryption process also :) . May be this folder will not provide much space. But atleast it will be help us.
Sounds like below solution might work for all versions -
(Storing videos, images and audio files that can not be accesses by others)
For this you have to store your data in External Storage with creating a folder name starting with the .(dot) then this folder is hidden from the FileExplorer.In this folder you need to create a .nomedia file and place your Images and video then images and videos are can not be visible to User in the Gallery.nomedia folder files can not be read by the gallery.
I created a rudimentary browser with help of a WebView.
When I visit a website (containing some text and a few images), the cache directory in /data/data/com.mayexample/cache/webViewCacheChromium gets filled with a few files called index, data_0, data_1, data_2, f_00001, f_00002 etc.
I was wondering, what's the format of these files, what do they contain? I thought about "so, a few of these files surely have to be the website's images then" and tried opening them in a file manager (open as image). But whatever file I pick, the process says "Failed loading!". Even if I rename some of them to .jpg, still I can't open anything.
I have read on the internet that this worked for some people though (look at Android WebView - Load Images from cache, it's exactly what I want to do), but I can't do anything with the cache's files.
Do you know a way to open webview cache files? Doesn't one file represent a corresponding cached image for example?
What I really want to achieve (once I understand the structure of the cache files) is to programmatically fetch images of the webview from it's cache, like the author of the link posted above (unfortunately this posting's answers don't help much)
Thanks!
The cached files might be one of the CSS, or JS/image/html types. On earlier Android releases, one cached file maps exactly one CSS or JS/image/html file.
As far as I know the Browser engine (actually the HTTP module) maintains such cached files in a URL-to-HashKey manner. So what you found (such as "4f42185de3a3a461_1") may be associated with any web resource files such as JS/CSS/images/HTML, etc.
I remember WebView used to store such URL-to-HashKey mapping data in sqlite3 tables in earlier Android versions.
The problem here is you have no idea about the mapping relations so you can hardly retrieve the file you want. A tricky way is to read the AOSP source code then you may be able to know how the generate the HashKey by an unique URL, or you can manipulate the sqlite3 tables, if there are still any on Android 4.4.
I was able to view files in the webViewCacheChromium folder using this tool designed for the Chrome desktop browser:
http://www.nirsoft.net/utils/chrome_cache_view.html
The contents are basically what you'd expect for web cache - images, javascript, css, html.
I'm looking for a solution (possibly implementing it) that would allow me to create a cache on my SD card (say, 25G) which would be used by some "caching layer" to store media on demand. The issue being that all of my media doesnt fit on a 32G SD card, so I want to be able to transparently have my missing data downloaded (ie: I dont want to have to download using another app) while I am attempting to play it in the stock media player or the Gallery or whatever. Does anyone know of an existing solution like this? I'm already familiar with the various dedicated apps out there which do this for music, but I'm looking more for a generic cache which caches anything that passes through it on an LRU basis. I want my NAS at home (or on S3, dropbox, Ubuntu One, whatever) to be an extension of my Android device(s).
If one doesnt exist, is it possible to chain ContentProviders? Could I create a CacheProvider which keeps track of what is currently in my cache and what isnt, and use the URIs from the CacheProvider to populate the MediaStore? All examples I can find so far show that what comes out of the MediaStore is always a URI to a file, so I suspect its not currently possible.
After a little POC, it appears that this is not possible using ContentProviders alone. While it is possible to chain providers together if you override ContentProvider.openfile(), since the MediaProvider currently assumes that everything in _data points to a file or an HTTP stream, the Music app will fail to play any content which sticks another content URI into _data. If MediaProvider were changed to handle content URIs then this would be possible, but I think the only way to achieve that would be to copy/paste the MediaStore framework and rename it all, or to do the change in a mod (like CyanogenMod).