Android OS 11+ pushing data files to an applications scoped storage - android

I have an Android algorithm library that I want to test inside an application that can run on real device. In order to run this test, I need to be able to push data files from a computer to the phone, to a location that can be read by the application being tested. Before OS 11 this could be done using the adb push ... command but the new scoped storage privacy feature will not allow me to do this anymore.
Whatever the solution is, it can not require manual human intervention as the intention of this test is to run automatically as part of a CI/CD pipeline.
Does anyone have any solutions? Thanks in advance for your suggestions!

You can add this permission to the Manifest:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
To grant this permission you need to enable it, but as far as I know this cannot be changed programatically, but you can use this adb command to change it via UI (Or you can write an UI Automator script to be more robust):
adb shell "am start -a android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION \
-d package:<REPLACE_WITH_YOUR_PACKAGE_NAME> ; \
sleep 2 ; \
input keyevent KEYCODE_DPAD_DOWN ; \
input keyevent KEYCODE_DPAD_DOWN ; \
input keyevent KEYCODE_DPAD_CENTER"
Now you can read your files normally:
#Test
fun myTest() {
val content = File("/sdcard/Download/myFile.txt")
.useLines(Charsets.UTF_8) {
it.joinToString("\n")
}
Log.d("TAG", content)
}

Related

how can I get adb permission on android 7.1?

my environment:
cpu:rk3288
os:android7.1
transfer method:sftp
I wrote android code to do these things below:
get the logcat with code "adb logcat -d -v time -f /mnt/sdcard/logcat.txt"
pull the file logcat.tx to the server with sftp
in 1st step I coding some java language with android studio like below, if anyone can help me, thanks!
Process p = Runtime.getRuntime().exec("adb logcat -d -v time -f /mnt/sdcard/logcat.txt");
error massage:
java.io.IOException: Cannot run program "adb": error=13, Permission denied
You can't use adb commands from inside the device, even if you somehow have it in the device you would need root permissions. The adb is the bridge between the PC and the device. Take a look here: https://developer.android.com/studio/command-line/adb
Although you probably can just remove the adb and use logcat directly, like:
Process p = Runtime.getRuntime().exec("logcat -d -v time -f /mnt/sdcard/logcat.txt");
Here you can see some more options about using the logcat command: Read logcat programmatically within application, including Reetpreet Brar's answer, that I think will be better for you:
Process pq=Runtime.getRuntime().exec("logcat v main"); //adapt the command for yourself
BufferedReader brq = new BufferedReader(new InputStreamReader(pq.getInputStream()));
String sq="";
while ((sq = brq.readLine()) != null)
{
//here you can do what you want with the log, like writing in a file to
//send to the server later.
}
Here you can choose methods to write your file: How to Read/Write String from a File in Android
Then, just send the file to the server.

Install APK using root, handling new limitations of "/data/local/tmp/" folder

Background
So far, I was able to install APK files using root (within the app), via this code:
pm install -t -f fullPathToApkFile
and if I want to (try to) install to sd-card :
pm install -t -s fullPathToApkFile
The problem
Recently, not sure from which Android version (issue exists on Android P beta, at least), the above method fails, showing me this message:
avc: denied { read } for scontext=u:r:system_server:s0 tcontext=u:object_r:sdcardfs:s0 tclass=file permissive=0
System server has no access to read file context u:object_r:sdcardfs:s0 (from path /storage/emulated/0/Download/FDroid.apk, context u:r:system_server:s0)
Error: Unable to open file: /storage/emulated/0/Download/FDroid.apk
Consider using a file under /data/local/tmp/
Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
Exception occurred while executing:
java.lang.IllegalArgumentException: Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
at com.android.server.pm.PackageManagerShellCommand.setParamsSize(PackageManagerShellCommand.java:306)
at com.android.server.pm.PackageManagerShellCommand.runInstall(PackageManagerShellCommand.java:884)
at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:138)
at android.os.ShellCommand.exec(ShellCommand.java:103)
at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:21125)
at android.os.Binder.shellCommand(Binder.java:634)
at android.os.Binder.onTransact(Binder.java:532)
at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:2806)
at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:3841)
at android.os.Binder.execTransact(Binder.java:731)
This seems to also affect popular apps such as "Titanium backup (pro)", which fails to restore apps.
What I've tried
Looking at what's written, it appears it lacks permission to install APK files that are not in /data/local/tmp/.
So I tried the next things, to see if I can overcome it:
set the access to the file (chmod 777) - didn't help.
grant permissions to my app, of both storage and REQUEST_INSTALL_PACKAGES (using ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent) - didn't help.
create a symlink to the file, so that it will be inside the /data/local/tmp/, using official API:
Os.symlink(fullPathToApkFile, symLinkFilePath)
This didn't do anything.
create a symlink using this :
ln -sf $fullPathToApkFile $symLinkFilePath
This partially worked. The file is there, as I can see it in Total Commander app, but when I try to check if it exists there, and when I try to install the APK from there, it fails.
Copy/move (using cp or mv) the file to the /data/local/tmp/ path, and then install from there. This worked, but it has disadvantages: moving is risky because it temporarily hides the original file, and it changes the timestamp of the original file. Copying is bad because of using extra space just for installing (even temporarily) and because it wastes time in doing so.
Copy the APK file, telling it to avoid actual copy (meaning hard link), using this command (taken from here) :
cp -p -r -l $fullPathToApkFile $tempFileParentPath"
This didn't work. It got me this error:
cp: /data/local/tmp/test.apk: Cross-device link
Checking what happens in other cases of installing apps. When you install via via the IDE, it actually does create the APK file in this special path, but if you install via the Play Store, simple APK install (via Intent) or adb (via PC), it doesn't.
Wrote about this here too: https://issuetracker.google.com/issues/80270303
The questions
Is there any way to overcome the disadvantages of installing the APK using root on this special path? Maybe even avoid handling this path at all?
Why does the OS suddenly require to use this path? Why not use the original path instead, just like in the other methods of installing apps? What do the other methods of installing apps do, that somehow avoids using the spacial path?
One solution, in case you don't mind the moving procedure, is to also save&restore the timestamp of the original file, as such:
val tempFileParentPath = "/data/local/tmp/"
val tempFilePath = tempFileParentPath + File(fullPathToApkFile).name
val apkTimestampTempFile = File(context.cacheDir, "apkTimestamp")
apkTimestampTempFile.delete()
apkTimestampTempFile.mkdirs()
apkTimestampTempFile.createNewFile()
root.runCommands("touch -r $fullPathToApkFile ${apkTimestampTempFile.absolutePath}")
root.runCommands("mv $fullPathToApkFile $tempFileParentPath")
root.runCommands("pm install -t -f $tempFilePath")
root.runCommands("mv $tempFilePath $fullPathToApkFile")
root.runCommands("touch -r ${apkTimestampTempFile.absolutePath} $fullPathToApkFile")
apkTimestampTempFile.delete()
It's still a bit dangerous, but better than copying files...
EDIT: Google has shown me a nice workaround for this (here) :
We don't support installation of APKs from random directories on the device. They either need to be installed directly from the host using 'adb install' or you have to stream the contents to install --
$ cat foo.apk | pm install -S APK_SIZE
While I think this is incorrect that they don't support installing of APK files from random paths (always worked before), the workaround does seem to work. All I needed to change in the code of installing an APK file is as such:
val length = File(fullPathToApkFile ).length()
commands.add("cat $fullPathToApkFile | pm install -S $length")
Thing is, now I have some other questions about it :
Does this workaround avoid the moving/copying of the APK into storage, and without affecting the original file ? - seems it does
Will this support any APK file, even large ones? - seems it succeeds in doing it for an APK that takes 433MB, so I think it's safe to use for all sizes.
This is needed only from Android P, right? - so far seems so.
Why does it need the file size as a parameter ? - No idea, but if I remove it, it won't work
Thanks for the answers! I looked everywhere else as well to get a whole setup for OTA to work for Android 10 and so on. It 100% works on Samsung Galaxy Tab 10.1 running Android 10.
Here is a medium article with the code:
https://medium.com/#jnishu1996/over-the-air-ota-updates-for-android-apps-download-apk-silent-apk-installation-auto-launch-8ee6f342197c
The magic is running this command with root access:
process = Runtime.getRuntime().exec("su");
out = process.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(out);
// Get all file permissions
dataOutputStream.writeBytes("chmod 777 " + file.getPath() + "\n");
// Perform silent installation command, all flags are necessary for some reason, only this works reliably post Android 10
String installCommand = "cat " + file.getAbsolutePath() + "| pm install -d -t -S " + file.length();
// Data to send to the LaunchActivity to the app knows it got updated and performs necessary functions to notify backend
// es stands for extraString
// In LaunchActivity onCreate(), you can get this data by running -> if (getIntent().getStringExtra("OTA").equals("true"))
String launchCommandIntentArguments = "--es OTA true --es messageId " + MyApplication.mLastSQSMessage.receiptHandle();
// Start a background thread to wait for 8 seconds before reopening the app's LaunchActivity, and pass necessary arguments
String launchCommand = "(sleep 8; am start -n co.getpresso.Presso/.activities.LaunchActivity " + launchCommandIntentArguments + ")&";
// The entire command is deployed with a ";" in the middle to launchCommand run after installCommand
String installAndLaunchCommand = installCommand + "; " + launchCommand;
// begins the installation
dataOutputStream.writeBytes(installAndLaunchCommand);
dataOutputStream.flush();
// Close the stream operation
dataOutputStream.close();
out.close();
int value = process.waitFor();

Enable/Disable wifi using Xamarin UiTest

I try to enable/disable wifi programmatically within my Xamarin Ui Test.
I already found this: Android: How to Enable/Disable Wifi or Internet Connection Programmatically. But it seems not to work within the UiTest.
I also tried something like this:
Context appContext = Android.App.Application.Context;
var wifiManager = (WifiManager)appContext.GetSystemService(Context.WifiService);
bool status = false;
wifiManager.SetWifiEnabled(status);
The first line (Android.App.Application.Context) throws an exception:
Message: System.IO.FileNotFoundException : Could not load file or assembly 'Java.Interop, Version=0.1.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065' or one of its dependencies. The system cannot find the file specified.
I'm using following namespaces:
using Android.Net.Wifi;
using Android.Content;
My project has a reference to Mono.Android.
The backdoor approach works fine for me.
The solution that works for me was a combination of:
Android: How to Enable/Disable Wifi or Internet Connection Programmatically
https://learn.microsoft.com/en-us/appcenter/test-cloud/uitest/working-with-backdoors
Some small own changes (for appcontext/context). Just context did not work for me.
1.: Add following line to the AndroidManifest.xml file of the Android project:
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
2.: Add following lines to the MainActivity.cs of the Android project:
using Java.Interop;
using Android.Net.Wifi;
[Export("ChangeWifiState")]
public void ChangeWifiState(bool state)
{
Context appContext = Android.App.Application.Context;
var wifiManager = (WifiManager)appContext.GetSystemService(WifiService);
wifiManager.SetWifiEnabled(state);
}
3.: Call following method out of the Xamarin Ui Test:
app.Invoke("ChangeWifiState", false); // true to enable wifi, false to disable wifi
PS: I use Xamarin Forms. I've got four different projects: a core project, an Android project, a Ui project, and a test project.
I just found a second solution without using the actual app.
It uses ADB commands to enable/disable wifi:
var process = new System.Diagnostics.Process();
var startInfo = new System.Diagnostics.ProcessStartInfo
{
WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
FileName = "cmd.exe",
Arguments = "/C adb shell am start -a android.intent.action.MAIN -n com.android.settings/.wifi.WifiSettings adb shell input keyevent 19 & adb shell input keyevent 19 & adb shell input keyevent 23 & adb shell input keyevent 82 & adb shell input tap 500 1000"
};
process.StartInfo = startInfo;
process.Start();
This can be used without a rooted device :).
Steps explained:
adb shell am start -a android.intent.action.MAIN -n com.android.settings/.wifi.WifiSettings opens the wifi settings.
adb shell input keyevent 23 enables/disables wifi.
I'm not sure why the command adb shell input keyevent 19 is used, but it works.
adb shell input keyevent 82 clicks the menu button to change back to the original app.
adb shell input tap 500 1000 clicks the coordinate x=500, y=1000 (center of screen).This may need be changed for different solutions.
Sources for this solution:
How to turn off Wifi via ADB?
ADB Shell Input Events
Run Command Prompt Commands

Android : Create a file in /data/local/tmp from an automated test

Goal:
Taking a screenshot during an automated test on a device. Pull the screenshot file using adb once the test is done.
Context:
I'm currently trying to write automated tests to take snapshots of the device screen. Using UiDevice to navigate, I would like to take a screenshot in the middle of a test. UiDevice has a method takeScreenshot that I call when I would like to take a snapshot.
After some investigation, I realised that the class responsible to write the image into file UiAutomatorBridge catches an Exception :
java.io.FileNotFoundException:
/data/local/tmp/screenshots/screen2.png: open failed: EACCES
(Permission denied)
Using adb, I created the file and set all permissions to all users.
adb shell touch /data/local/tmp/screenshots/screen1.png
adb shell chmod 777 /data/local/tmp/screenshots
Once done, I can take a screenshot with :
#Test
public void takeSnapShot() {
String filename = "/data/local/tmp/screenshots/screen1.png";
File file = new File(filename);
assertEquals(true, mDevice.takeScreenshot(file));
}
Problem :
I would like to be able to create a file directly while the test is executing, without the need of using adb.
I tried to create a file directly from Java using createNewFile.
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
But I get an IOException
java.io.IOException: open failed: EACCES (Permission denied)
Has anyone an idea of what is going on ? I'm quite new to Linux, so don't hesitate to suggest something even if it seems obvious to you ;)
Should I post this on superuser instead ?
EDIT
The directory /data has these permissions
drwxrwx--x system system 2016-01-14 14:03 data
I can't list the content of /data, which makes me believe the user "shell" doesn't belong to the group "system". I can't list the content of /data/local neither.
However, the /data/local/tmp is owned by "shell".
drwxrwx--x shell shell 2016-01-14 12:20 . (tmp)
/data/local/tmp gives +x permission to all users.
Finally, the directory "screenshots" belongs to shell with permissions 777.
drwxrwxrwx shell shell 2016-01-14 11:46 screenshots
To my understanding, any user should be able to access /data/local/tmp/screenshots
Instead of saving under /data/local/tmp/, use Environment.getExternalStorageDirectory().
You need a read/write permission to do that. You have to add these two lines into the Manifest of the application ! (I tried to add those lines into the /androidTest/Manifest.xml file, but it has no effects).
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
While it solves the problem of finding a common directory to save/read screenshots, it brings a new one as well.
Having to add these 2 permissions isn't a big deal if you already had them. But if you don't want to ask the user for those permissions, you have to add/remove these lines every time you test/sign your application, which is far from ideal.
EDIT :
In this post, someone proposed to make use of the build differentials (debug, release) to have two different Manifests.
In the end, I have 3 Manifests :
Release Manifest (without external read/write permissions)
Debug Manifest (with external read/write permissions)
AndroidTest Manifest for
tools:overrideLibrary="android.support.test.uiautomator.v18"

Libgit2 running on Android unable to perform clone operation - returns "failed to set permissions" error

I have just built Libgit2 (v0.20.0) for Android (target SDK version 18, debugging on a rooted device running Cyanogenmod 10.1.2, Android 4.2.2) and a simple function like getting the version number of Libgit2 works fine through the JNI. But when I use the git_clone function it stops right after the objects/info folder is created and returns this error:
Error -1 cloning repository - Failed to set permissions on '/storage/sdcard0/it/ptt/.git/objects/info': Operation not permitted
I have given the application the WRITE_EXTERNAL_STORAGE permission but I guess it still can't chmod unless owner of the file. When I use adb shell to check out the permission mode of the info folder I get:
d---rwxr-x system sdcard_rw 2014-05-15 09:31 info
And by using pwd.h functions I get the username that the c code (that is calling git_clone) is under to be u0_a92. How am I suppose to get pass this I suppose very Android related issue? Is there a simple way to stop Libgit2 from calling p_chmod or can I give it permissions to do so?
I ended up defining p_chmod as a method always returning true to get passed the error. In the bash script I use to build libgit2 I inserted the following lines that leaves the source files in an unmodified condition after building for android:
LIBGIT2_POSIX_PATH="$LIBGIT2_SOURCE_PATH/src/posix.h"
LIBGIT2_POSIX_BACKUP_PATH="$LIBGIT2_SOURCE_PATH/src/posix_original.h"
printf "#include \"always_success.h\"\nint always_success() { return 0; }" > "$LIBGIT2_SOURCE_PATH/src/always_success.c"
printf "int always_success();" > "$LIBGIT2_SOURCE_PATH/src/always_success.h"
cp $LIBGIT2_POSIX_PATH "$LIBGIT2_POSIX_BACKUP_PATH"
sed -i "s/^#define\sp_chmod(p, m).*$/#include \"always_success.h\"\n#define p_chmod(p, m) always_success()\nextern int always_success();\n/" $LIBGIT2_POSIX_PATH
# run the build process with cmake ...
# restore chmod manipulated source header
mv $LIBGIT2_POSIX_BACKUP_PATH $LIBGIT2_POSIX_PATH
There is probably a cleaner way to solve this but at least now I dont get that error anymore. Thanks to Carlos for his help!
UPDATE
Running adb shell mount | grep sdcard I could see that the sdcard which I am trying to clone the repository into uses the vfat file system which according to this forum thread doesn't support unix-style permissions.

Categories

Resources