android:sharedUserId="android.uid.system" Obtaining Files from SDCard - android

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.

Related

Android: ANR (Activity Not Responding) on getExternalStorageDirectory()

In the Google Play Console there is a very helpful page “Android vitals” / “ANRs & crashes”. The information provided under “crashes” is almost helpful (e.g. NullPointerException in "filename, line number"). That is something to work with. However, the info under “ANRs” (Activity Not Responding) is mostly mysterious.
For example, I have written an app which needs read/write access to the external storage – which is not necessarily a removable SD Card. Since Android 4.2 (API 17), access to the external SD Card is not possible any more. Instead, there is a link to “/mnt/sdcard” or "/storage/emulated/0", which resides in the internal memory. (Why this is called “external storage” is Google’s secret.)
Of course I have set the required entries in AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
and of course, since Android 6 (Marshmallow), I am handling the permissions correctly with
requestPermissions(requPerms, myRequestCode);
and
onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults)
To determine the storage path for the app data files (for example "/storage/emulated/0"), I use the following code:
public static File getMountName()
{
File mount = null;
// possible ANR 1
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED))
{
// possible ANR 2
File path = Environment.getExternalStorageDirectory();
mount = new File(path.getAbsolutePath());
}
return mount;
} // getMountName
For some reason, some of the user’s smartphones are hanging with “ANR” in one of the two lines “Environment.getExternalStorage…” marked with the respective comment. (Which means, if they hang on the 2nd line, they have passed the 1st one successfully.)
The related error messages in Android vitals are shown in the following examples:
for ANR 1:
Input dispatching timed out (Waiting to send non-key event because the
touched window has not finished processing certain input events that
were delivered to it over 500.0ms ago. Wait queue length: 2. Wait
queue head age: 14805.0ms.)
and for ANR 2:
Context.startForegroundService() did not then call
Service.startForeground(): ServiceRecord{33def99 u0
net.braun_home.sensorrecording.lite/net.braun_home.sensorrecording.SensorService}
Does anybody have an idea, what these messages mean, and why they appear on some smartphones only? According to Android vitals, they all have newer Android versions 8 or 9. I myself have also two devices with these versions and the app runs perfectly there.

Does DocumentsContract.deleteDocument cancel takePersistableUriPermission?

In my app the user can create a backup, with the following workflow:
user selects a destination (opening SAF UI and selecting a destination)
user chooses some options
user either presses a start button to create the backup, or cancels and goes back
I'm trying to do this with Storage Access Framework (since with Android 10 we are forced to use that... thing). I'm using an ACTION_CREATE_DOCUMENT intent. When doing so, Android creates an empty file as soon as the user picks a destination.
If the app crashes / is killed, or if the user goes back, the empty file will still be there (in case of the user going back of course I can delete it in onDestroy, but that will not work if there's a crash / kill).
To reduce the risk of having that empty file, I'd like to delete it in onActivityResult, and create it only if the user effectively creates the backup.
To do so, in onActivityResult I call takePersistableUriPermission, and then DocumentsContract.deleteDocument. But then when trying to create the file the app crashes:
Permission Denial: writing com.android.externalstorage.ExternalStorageProvider uri [...] from pid=9543, uid=10136 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()
It works if I do not call DocumentsContract.deleteDocument, so my writing code seems correct (I'm using getContentResolver().openOutputStream).
So, the question is: is it crashing because calling DocumentsContract.deleteDocument revoked my write permission, even though I called takePersistableUriPermission??
If so, is there any way to delete the file while keeping the write permission? android.permission.MANAGE_DOCUMENTS is reserved to system apps, but is there a way with grantUriPermission()?
Even better, is there a way to prevent SAF to create a dummy empty file??

DownloadManager runs MediaScanner after download but shouldn't

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.

Run a service with Root privileges or adding permissions with root

I am currently developing an app that reads out SMS/Emails while driving. Many users wished support for WhatsApp / KakaoTalk.
However, as there is no "official" way to receive their messages, there would be only three options, all requiring root:
The easier way of scanning their database in a given intervall.
Easy to implement.
However not battery efficient
Also the messages are not read out immediately.
An other way would be to run a service with root rights and register a receiver that listens for their push notifications. This has to be done with root, as both packages require a signature based permission for receiving their push notifications.
Harder to implement
Better user experience
Also another thing came to my mind: Would it be possible to manually add permissions to an APK after installing? In that case I could add the c2dm permissions to my package.
This would make things very easy
However, I am a little bit scared of changing my app's permissions, as this is completely against the Android Sandbox principle.
Still, if it would be possible, let me know!
The problem is, how exactly do I run a service with root rights (is it actually possible)? I know how to run shell commands or binaries with root, but I have no idea how to start a part of an APK as root.
Also, would it be possible to integrate a BroadcastReceiver into a binary? I have actually no experience with C/C++, especially in an android environment.
Can you help me with that?
Thanks.
edit: Like I said in the comment, I do not want to use an AccesibilityService, as it does not fit my needs (eg it will give me "2 unread messages" if more then one is unread, also it does not include the full body).
edit2: Just to clarify things: I know how to run commands with root. What I need to know is how to register a Broadcastreceiver, that receives a specific broadcast "normal" receivers don't get, as the Broadcast itself requires a signature based permission I don't have.
This is far from trivial but should work when the apps you want to monitor use sqlite databases or, more generically, write messages to a file when they arrive.
You will indeed need to have root access to the device as this violates the android security system:
Write a native process which runs as a daemon using the NDK and spawn it once after boot as root. You have now 3 major problems to solve:
How to find out if something changed?
This is the easy part. You would need to utilize the Linux inotify interface which should be accessible on every Android phone as the SDK has a FileObserver since API 1, so you are on the safe side here.
Another interesting approach may be to catch the C2DM messages. I have found a NDK class called BroadcastReceiver, so the NDK may be able to catch them. But I personally wouldn't do that as it feels wrong to steal intents. Also you would have to redistribute them or let them travel to real recipient, so I will not describe this in detail here. It may work, but it may be harder and should only be a fallback.
So, when you have solved this, the next problem arises:
How to read the changes in a safe way?
You have a problem, a big one, here. The file doesn't belong to the client, and the client doesn't even have the permission to know where it is (normally). So the monitored app is not aware of the client and will act like the file is exclusively owned only by itself. If they use some plain old textfile to write messages to you have to find out a way to read from it safely as it may be overwritten or extended at any time. But you may be lucky when they use sqlite, according to this it's totally valid to have more than one simultaneous reader, just only one writer. We are in the specs, everything fine again. When you have now read out the new data, more problems to solve:
How to get the new data back into the main app?
You should do only the bare minimum in this C/C++ program because it runs as root. You should also protect your app users from security breaches, so please write the program with this in mind. I have no real idea for this could work really good, but here are some thoughts:
Write the collected data into your own sqlite database (easy in C/C++ and Java),
Write the collected data into a plain file (not recommended at all, pain in the rear),
Send an Intent which contains the new data (maybe not that easy in C/C++, but easy in Java)
Use sockets/pipes/..., just every RPC mechanism you could imagine which is brought to you by Linux (same as the file, don't do it)
As stated in the text above, please be careful when you write this daemon as it is a potential security hazard. It may be hard to do this when you have no knowledge about C/C++ at all, even if you have written simple programs this should be a non trivial task.
On my search through the web I have found the NDK C++ classes I mentioned above. It can be found at Google code. I have neither experience with the NDK nor the C++ wrapper but it may be worth a look when you plan to write this.
Force, I must tell you that an Android Service do not require root access instead some actions(i.e. Access, Read, Write system resources) requires Root Permissions. Every Android Service provided in Android SDK can be run without ROOT ACCESS.
You can make the actions to execute with root permissions with the help of shell commands.
I have created an abstract class to help you with that
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import android.util.Log;
public abstract class RootAccess {
private static final String TAG = "RootAccess";
protected abstract ArrayList<String> runCommandsWithRootAccess();
//Check for Root Access
public static boolean hasRootAccess() {
boolean rootBoolean = false;
Process suProcess;
try {
suProcess = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(suProcess.getOutputStream());
DataInputStream is = new DataInputStream(suProcess.getInputStream());
if (os != null && is != null) {
// Getting current user's UID to check for Root Access
os.writeBytes("id\n");
os.flush();
String outputSTR = is.readLine();
boolean exitSu = false;
if (outputSTR == null) {
rootBoolean = false;
exitSu = false;
Log.d(TAG, "Can't get Root Access or Root Access deneid by user");
} else if (outputSTR.contains("uid=0")) {
//If is contains uid=0, It means Root Access is granted
rootBoolean = true;
exitSu = true;
Log.d(TAG, "Root Access Granted");
} else {
rootBoolean = false;
exitSu = true;
Log.d(TAG, "Root Access Rejected: " + is.readLine());
}
if (exitSu) {
os.writeBytes("exit\n");
os.flush();
}
}
} catch (Exception e) {
rootBoolean = false;
Log.d(TAG, "Root access rejected [" + e.getClass().getName() + "] : " + e.getMessage());
}
return rootBoolean;
}
//Execute commands with ROOT Permission
public final boolean execute() {
boolean rootBoolean = false;
try {
ArrayList<String> commands = runCommandsWithRootAccess();
if ( commands != null && commands.size() > 0) {
Process suProcess = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(suProcess.getOutputStream());
// Execute commands with ROOT Permission
for (String currentCommand : commands) {
os.writeBytes(currentCommand + "\n");
os.flush();
}
os.writeBytes("exit\n");
os.flush();
try {
int suProcessRetval = suProcess.waitFor();
if ( suProcessRetval != 255) {
// Root Access granted
rootBoolean = true;
} else {
// Root Access denied
rootBoolean = false;
}
} catch (Exception ex) {
Log.e(TAG, "Error executing Root Action", ex);
}
}
} catch (IOException ex) {
Log.w(TAG, "Can't get Root Access", ex);
} catch (SecurityException ex) {
Log.w(TAG, "Can't get Root Access", ex);
} catch (Exception ex) {
Log.w(TAG, "Error executing operation", ex);
}
return rootBoolean;
}
}
Extend your class with RootAccess or create an instance of RootAccess class and Override runCommandsWithRootAccess() method.
running something as root is not the right way of solving this.
instead, consider an accessibility service that can watch for new notifications:
AccessibilityEvent
It is not possible to run a Service (or any other application component for that matter) as root, if you are targeting unaltered, non-rooted devices. Allowing that would make all security mechanisms in Android pointless.
It is not possible to alter the permissions of an APK at runtime either. Permissions are always granted or rejected at APK install-time. Please refer to http://developer.android.com/guide/topics/security/security.html for some more info on the subject.
"What I need to know is how to register a Broadcastreceiver, that receives a specific broadcast "normal" receivers don't get, as the Broadcast itself requires a signature based permission I don't have."
You can't. Period. End of story. And thank ghod for that.
Yes, if you use the scary rooted device facilities to have some code run as root, you can in theory do whatever you want. In practice, it may be quite hard to get around this restriction, and the platform is often designed to be that way. You will at the very least need to mess around with the state maintained and/or stored by the package manager, and will likely need to cause the user to reboot the device to get changes you make as root to actually have an impact. And of course you are then messing with deeply internal implementation details of the platform, which means breaking all over the place across different versions of the platform and different builds from different manufacturers.
you can use
pm grant your.permission
as a shell command to grant additional permissions to your app.
I think that command was added quite recently, so if you target older versions you may have to directly alter the 'packages.xml'.
It is possible to execute an app/dex file as root with the app_process command, but I haven't figured out yet how to get a valid context (with this you can use the java.io.File api to access all files, but non static android methods like bindService etc. will fail because you are running without an app context).
Of course you can change the permissions of your applications. If the permissions will be changed, the user will just have to manually update the app, and the new permission will be displayed to the user. But I do not exactly know how changing your app permission will help you in solving this problem.
Another thing I can tell you, is that you can not run a Service or whatever as root, only on rooted devices, and it will not be an easy task to root the devices through your application, and also it won't be something that many user will want.
How are you currently accessing the SMS?
If you have a BroadcastReceiveryou could set the MAX_PRIORITY for your receiver and maybe it will intercept the messages before other applications. This can be done as follows:
<receiver android:name=".SmsReceiver" >
<intent-filter android:priority="100" >
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
You could also use the SMS Provider, which is not public now but maybe if you query at a given interval this Provider you can check for new messages. You could also have a look at this thread : Android SMS Provider if you have not done this allready.

Android: Problem with Media Scanner not Running

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)

Categories

Resources