I have an app that user's can draw with, and then 'export' that drawing as a .png file to external storage, if present. Generating the PNG, copying the file to external all work like a charm, but a rather unique problem happens; after the export, if the user navigates to the image via My files (Samsung Tab running 2.2 in this case), they can see the .png file, but when they open it, the screen is black for about 10 seconds... then they see the image, Additionally, the images don't show up in the user's 'Gallery' app either.
Now, if the user connects the device to the computer via USB, or reboots the device, they can access the images no problem from My files, and they appear in 'Gallery' from that point forward, but again, any newly esported files experience the same problems until they cycle/connect the device again.
My thinking was that this had to be related to the Media Scanner (at least in the case of the 'Gallery' problem, it most certainly is).
So, as I am targetting Api 8+, I am trying to use the static MediaScannerConnection.scanFile() method to have the OS re-scan and add my images into the Gallery, etc. Also hoping this solves the issue of the strange delay in opening the images. Here is my code:
MediaScannerConnection.scanFile(
context,
new String[] { "/mnt/sdcard/MyApp" },
null,
null
);
LogCat gives me the following entries when I export an image, and thus run the above call:
DEBUG/MediaScannerService(2567): IMediaScannerService.scanFile: /mnt/sdcard/MyApp mimeType: null
DEBUG/MediaScannerService(2567): onStartCommand : intent - Intent { cmp=com.android.providers.media/.MediaScannerService (has extras) }
DEBUG/MediaScannerService(2567): onStartCommand : flags [0], startId [1]
DEBUG/MediaScannerService(2567): ServiceHandler:handleMessage volume[null], filePath[/mnt/sdcard/MyApp]
DEBUG/MediaProvider(2567): getSdSerial() sd state = removed
INFO/Database(2567): sqlite returned: error code = 17, msg = prepared statement aborts at 43: [SELECT DISTINCT sd_serial FROM images WHERE sd_serial LIKE 'external_0x%']
ERROR/MediaProvider(2567): removeMediaDBData called
DEBUG/MediaScanner(2567): prescan enter: path - /mnt/sdcard/MyApp
DEBUG/MediaScanner(2567): prescan return
So, it looks like the MediaScanner is getting the correct location, but is failing to find the SD card, which is correct, and failing. The Samsung Tab has built-in non-SD external storage, which Android gives access to via Environment.getExternalStorageDirectory(). How do I tell it to scan the non-SD storage?
Any ideas how to proceed?
Paul
Found the solution here, which involves sending a broadcast request to the media scanner via an Intent:
How to update the Android media database
Never did figure out the issue with MediaScannerConnection.scanFile.
Whenever you add a file, let MediaStore Content Provider knows about it using
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageAddedOrDeleted)));
Main advantage: work with any mime type supported by MediaStore
For deletion: just use getContentResolver().delete(uri, null, null)
Related
I have a scenario in Android (SDK 19/KitKat 4.4.2) where my application is to be signed as a system level application (App 1) using android:sharedUserId="android.uid.system" in the Manifest.xml. This means that this application is unable to write or read from SD cards, be they external or built into the device.
If I needed to obtain a large file from the SDCard and read it into my application, what is the best approach to do this?
My goal is simply to obtain image files from the SDCard. However, even images can be relatively big if they're uncompressed bitmaps.
I've tried the following approaches:
Creating a new application that is not signed as the system user (App2). Starting a service that exists in this App2 from App1, then reading in the file from the SD card from here, then obtaining the byte[] of the file, and sending it over via AIDL to App1 in chunks. This works in terms of reading the file from the SDCard and sending it over, however AIDL has a cap of 1mb for each transaction and is also very slow to a point where I should probably limit the size of images allowed to be given to the application to make this feature usable. Not the most ideal in my opinion.
I've tried using FileProvider in App 2 (UID: 10007), however in this scenario I need to not open any graphical interface to select the file I want and a target application. I need to just send it over immediately to App 1 (UID: 10047) or obtain it immediately from App1. I'm not sure if it's possible to use FileProvider without those gui steps. I tried just creating the Uri from App2 then sending the Uri to App1 over AIDL, then giving permissions via context.grantUriPermissions(packageName,uri,READ/WRITE), but always end up with a security error where App1 does not have permission to read the uri App2 is providing.
java.lang.SecurityException: No permission grant found for UID 10047 and Uri content://com.test.sdcard/folder/img.png
Where UID 10047 is App 1 and UID 10007 is App 2.
Any alternative solutions to this problem?
A system app can't read from external storage? That's news to me, but anyway.
You can always just create a pipe and pass the read end back over the IPC mechanism of your choice (ContentProvider.call() for example). The service-side starts a thread and writes the file to the write end, and passes the read end back to the client. Something like this on the service side:
ParcelFileDescriptor[] fds;
try {
fds = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
e.printStackTrace();
return false;
}
final ParcelFileDescriptor readFd = fds[0];
final ParcelFileDescriptor writeFd = fds[1];
// TODO: start a thread to write file to writeFd
Bundle result = new Bundle();
extras.putParcelable(EXTRA_READ_FD, readFd);
return result; // Return from call() to client
Obviously there is a lot of boilerplate code left as an exercise for the reader.
I am interested in writing a screenshot app and want to learn the technique from this app.
After user takes a screenshot using power and volume buttons, the app opens up the screenshot without the user needing to pick an image from the gallery. I want to do something similar (save the user the step to navigate the gallery to get screenshots).
Does anyone how can an app read a screenshot as this app did ? (In the app's demo video this step is shown at time 0:30)
Edit
I've tried testing it on my nexus 5. I can see the screen shots are in folder /sdcard/Pictures/Screenshots. The directory permissions are:
drwxrwx--x root sdcard_rw 2015-08-30 01:42 Screenshots
I gave my app storage permissions. I used the following code in a service, but it didn't work:
FileObserver fileObserver = new FileObserver("/sdcard/Pictures/Screenshots") {
#Override
public void onEvent(int event, String path) {
Log.d("Test", "FileObserver event");
}
};
fileObserver.startWatching();
It should be running as a service in the background, which register a FileObserver, and perform action upon file added.
Or the service simply check the folder manually.
Edit:
Warning: If a FileObserver is garbage collected, it will stop sending events. To ensure you keep receiving events, you must keep a reference to the FileObserver instance from some other live object.
It means that local variable is definitely not working, put it into a field AND make sure your class will not be garbage collected, e.g. even Activity can be killed, and garbage collected afterwards.
it seems that MediaScanner wants to scan files that I told it not to. Now I wonder why.
My app downloads several media files from my server and shows them later with a playlist.
For that, the app gets the media files with the Android system's DownloadManager.
Using Request.setDestinationUri(), the download will be saved to a subdirectory of getExternalCacheDir() named "pending".
When the download is finished, the Android DownloadManager sends ACTION_DOWNLOAD_COMPLETE broadcast. My app's broadcast listener will then take that finished download and move it from the "pending" folder to a different folder named "media".
All this works as intended.
However, the system log is full of messages like these:
E/BitmapFactory(23779): Unable to decode stream: java.io.FileNotFoundException: /path/to/pending/image.jpg: open failed: ENOENT (No such file or directory)
E/JHEAD (23779): can't open '/path/to/pending/image.jpg'
E/StagefrightMetadataRetriever(25911): Unable to create data source for '/path/to/pending/video.mp4'.
E/MediaScannerJNI(23779): An error occurred while scanning file '/path/to/pending/video.mp4'.
So apparently, my app tells the DownloadManager to download an image / a video to the "pending" directory. It does as it's told and sends a "I completed the download" broadcast. My app receives the broadcast and moves the complete file to the "media" directory. Some moments later, the MediaScanner (or something else) tries to scan the completed file in the "pending" folder and barfs into the system log.
Now I'm wondering: Why is MediaScanner trying to read these files, anyway?
According to the Android API doc for setDestinationUri: "The downloaded file is not scanned by MediaScanner. But it can be made scannable by calling allowScanningByMediaScanner()." I don't call that method, so the downloaded file should not be scanned.
Next, I tried to put an empty .ignore in the app's cache directory and reminded the MediaScanner of the .ignore-file's existence through ACTION_MEDIA_SCANNER_SCAN_FILE, but the error messages remain.
To add to the mystique, the files do not show up in the system's gallery or video apps, so yes, the media scanner ignores them. But still: Why does it try to read them when it doesn't have to? Is it the MediaScanner at all or is it some other system service?
Next, I tried to put an empty .ignore in the app's cache directory and reminded the MediaScanner of the .ignore-file's existence through ACTION_MEDIA_SCANNER_SCAN_FILE, but the error messages remain.
The magic file to stop the Mediascanner is not ".ignore" but ".nomedia".
From MediaScanner.java:
File file = new File(path.substring(0, slashIndex) + ".nomedia");
if (file.exists()) {
// we have a .nomedia in one of the parent directories
return true;
}
And the reason why it did not appear in the system's gallery or video apps is maybe, because the scan crashed (as indicated in the log).
However, I have bad feelings about the media scanner too. For example, why doesn't it stop scanning in a more straight way. For example, in MediaScanner.java, instead of
public void scanDirectories(String[] directories, String volumeName) {
[...]
for (int i = 0; i < directories.length; i++) {
processDirectory(directories[i], mClient);
}
it could be
public void scanDirectories(String[] directories, String volumeName) {
[...]
for (int i = 0; i < directories.length; i++) {
if (! isNoMediaPath(directories[i]))
processDirectory(directories[i], mClient);
}
But instead, it goes forth and back between java code and cpp code again and again. What is the reason for that?
It really looks like the ".nomedia" won't give even half the effect some might expect. After further investigating in MediaScanner.java I would say, that this file does not stop the MediaScanner from scanning the whole tree at all. It still adds entries to MediaStore for each file, never mind of noMedia being set or not.
It just marks those entries in the MediaStore as "must not show up". On each file it does an "beginFile" and an "endFile". In the endFile it always does a mMediaInserter.insert, the one way or the other.
What bothers me so much about this is the fact, that it scans through all the files in e.g. a mounted stick, hereby taking the risk of trapping into a virus (specially designed for that scan process) and no .nomedia file can stop it from doing so.
On some customer devices calling getExternalFilesDir( null ) (and rarely getFilesDir()) from Application.onCreate() returns null.
The documentation says nothing about not being able to call these methods from an Application object and on most devices they work just fine. Anybody have any idea why these methods would return null and should I call them exclusively from an Activity object?
Add
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" >
</uses-permission>
to your Manifest.
Why Write not Read (when only reading the directory)- I do not know. I suppose every application need it in order to create the directories when using the sdcard for the first time.
From the docs for ContextWrapper.getExternalFilesDir
Returns
Returns the path of the directory holding application files on external storage. Returns null if external storage is not currently mounted so it could not ensure the path exists; you will need to call this method again when it is available.
Are you checking to make sure the external storage is mounted before calling that method?
Some of our clients are also getting this. Luckily, it suddenly reproduced on one of our test devices.
After some sniffing around I found that a weird file appeared under Android/data/ named like the app package name but with a size of 0. I couldn't delete or rename it. While this file existed, the app could not access external storage.
After some attempts, I rebooted the device and navigated to Android/data. I saw that the strange file disappeared and a folder of the same name appeared. The application started functioning normally.
I assume that this is a bug in Android application installer. I will start advising clients that report this issue to restart their devices.
Two of my users have reported a problem with my Android application, OftSeen Gestures. Both of them are using a Motorola Droid. The app saves a text file which is just a list of gesture names and phone numbers, both strings. It saves the file to the private data area. I don't know that it is this code that is failing but they report the assigned numbers disappearing after the phone comes out of screen sleep. Since the file is reread in OnCreate each time, I'm assuming the file doesn't exist on return.
As soon as I can get my hands on a Droid I will debug it but in the meantime can you see a reason why this save operation would fail on Droid (no other users have reported this)?
OutputStreamWriter out = new OutputStreamWriter(AppGlobal.getContext().openFileOutput(MAPPINGS_FILE_NAME, 0));
for (String key : mMap.keySet()) {
String number = mMap.get(key).number;
out.write(String.format("%s,%s\n", key, number == null ? "" : number));
}
out.close();
AppGlobal.getContext returns the application context and the MAPPINGS_FILE_NAME resolves to "gesture_mappings.txt".
Like I say, I don't know that this is the problem. It could be something else to do with state management inside the app. If anyone has a Droid, maybe they could download the app from Market and test it for me? Note this is a genuine request for help - not an attempt to increase my downloads.
This was diagnosed as being caused by extended character sets causing line-breaks to be misinterpreted and was solved by explicitly writing the file using UTF8. See How to read and write UTF-8 to disk on the Android? 1