I have a largely native Android game on my hands which has to employ APK expansion files for the usual reasons. The project targets API level 21, and is 64-bit only.
Downloading the APK expansion works.
Mounting does not work: for an unencrypted obb, I get an error state 20 (AOBB_STATE_ERROR_INTERNAL). For an encrypted one I get a 21 (AOBB_STATE_ERROR_COULD_NOT_MOUNT).
The APK expansion is created with the jobb tool.
The versionCode is matching the one in my AndroidManifest.xml.
The key (when used), is correct.
The obb file obviously exists, and I'm getting the obb mount path by the way of
String packageName = getPackageName(); // we're in the activity
PackageManager pm = getPackageManager();
return Environment.getExternalStorageDirectory().getAbsolutePath() +
EXP_PATH + packageName + "/main." +
pm.getPackageInfo(packageName, 0).versionCode + "." + packageName +
".obb";`.
The result I get is the same both with an NDK implementation and with a Java implementation.
I understand that this was broken for years, but it has been fixed. Is it really? What am I doing wrong?
After hours and hours of digging, and nearly abandoning hope all, I've found success. And it's ridiculous. But it works.
Make sure that the path you're feeding to storageManager.mountObb() (or AStorageManager_mountObb()) has any leading slashes removed. I.e.
if (obbPath != null && obbPath.startsWith('/'))
{
obbPath = obbPath.substring(1);
}
The one that you get from Environment.getExternalStorageDirectory() will have one.
Hope this helps save someone time.
Related
I looked for solutions from many places (Test programmatically if in Android 10+ the legacy external storage access is disabled, https://developer.android.com/google/play/expansion-files.html#Downloading, etc.), but don't know the exact cause of the error.
If app is downloaded from Google Play only in case for android 10 devices, it gives this error:
Caused by: java.lang.IllegalArgumentException: Couldn't get OBB info for /storage/emulated/0/Android/obb/packageName/main.package_version.packageName.obb
The program can find the obb file (even on android 10 devices)
File mainFile = new File(Environment.getExternalStorageDirectory() + "/Android/obb/" + packageName + "/" + "main." + packageVersion + "." + packageName + ".obb")
mainFile.getAbsolutpath()
gets the file path too. However, when Storage Manager is used for app that is downloaded from Google Play store
sm.mountObb(mainFile.getAbsolutePath(), null, mainObbStateChangeListener );
it cannot reach obb expansion file and terminates app with above error.
App can be downloaded from Google Play, but works only for android 9 or below devices. If I create apk and install it on android 10 device, the app can reach obb file without any problem.
What do I miss to be able run app on android 10 devices when it is downloaded from Google Play store? Help would be appreciated.
Update:
I found the cause of the error on Android 10 device, though I still don't know the answer:
If the apk version code (the version code in app gradle) matches to the version code in the obb file name (which it should be if I understood well (https://developer.android.com/google/play/expansion-files), then in the obbexpansionmanager class, the line
sm.mountObb(mainFile.getAbsolutePath(), null, mainObbStateChangeListener );
throws this error:
Caused by: java.lang.IllegalArgumentException: Couldn't get OBB info for /storage/emulated/0/Android/obb/packageName/main.package_version.packageName.obb.
If I change the downloaded app's obb file to a different version code (tested with version code 8, 1, 10 when apk's version code was 9) then app starts and above error does not come up. However, I cannot use different version code in the obb file's name other than the apk's version code to be able to download apk with obb from Google Play store. Furthermore, the media files cannot be reached from obb file unless it matches version codes.
If the device is under Android 10 then app works with same version code in apk and in obb expansion file.
A substitute method can be used instead of Environment.getExternalStorageDirectory() which is Context.getObbDir() which will return the full path of the OBB directory meant for the running application, i.e.: /storage/emulated/0/Android/obb/packageName.
This can replace the code part Environment.getExternalStorageDirectory() + "/Android/obb/" + packageName. More details of this method can be found here.
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();
I am trying to save a bmp into file in my C++ android app (I am working with chromium project).
FILE* fp = fopen("/myimage.bmp", "wb"); // result: fp==NULL, errno==30
FILE* fp = fopen("/Pictures/myimage.bmp", "wb"); // result: fp==NULL, errno==2
Phone is Nexus 5x having no sdcard. android.permission.WRITE_EXTERNAL_STORAGE and android.permission.WRITE_INTERNAL_STORAGE are set.
Looks like I am using invalid path? Which path would be valid? It is a debug output, so I need any path that would work.
If you're accessing your internal storage of your application then the root path of your application storage is
String ROOT_PATH = "/data/data/" + "com.your.package.name" + "/";
So in case of accessing your file from your internal storage you have to declare the file path like this.
FILE* fp = fopen(ROOT_PATH + "myimage.bmp", "wb");
But this doesn't ensure your problem to be solved as #CommonsWare suggested,
"the root path of your application storage is" -- the path varies by
OS version, user account (primary vs. secondary), and possibly device
manufacturer. NEVER HARDCODE PATHS. If NDK code needs to write to a
location, Java code should be passing in the path, where that is
derived from a method (e.g., getFilesDir()).
So I'm referring to his comment again in your question.
You cannot write to / on any Linux-based system without
superuser-level privileges. "I am working with chromium project" --
then figure out where Chromium writes files, and write your files
there.
These are some valuable insights suggested by #CommonsWare which might help you. Thanks.
I am looking for a solution for copying all the files from a specific directory on the hard drive, to a specific or non specific directory on my android phone, once this device is connected.
I would like these files to be automatically moved (or at least copied) to my phone once I connect it to the computer and run the .py file.
I have windows 7 and python 2.7
I was trying this from another answer but I can't understand because there is few explanation, therefore I cannot get it to work.
edit: I have figured out how to transfer files between to folders but I want to my phone. So how can I fix the error of my system not finding the path of my phone, that'll fix my problem I believe. The code works fine the problem is the path.
Here is my code:
import os
import shutil
sourcePath = r'C:\Users\...\What_to_copy_to_phone'
destPath = r'Computer\XT1032\Internal storage'
for root, dirs, files in os.walk(sourcePath):
#figure out where we're going
dest = destPath + root.replace(sourcePath, '')
#if we're in a directory that doesn't exist in the destination folder
#then create a new folder
if not os.path.isdir(dest):
os.mkdir(dest)
print 'Directory created at: ' + dest
#loop through all files in the directory
for f in files:
#compute current (old) & new file locations
oldLoc = root + '\\' + f
newLoc = dest + '\\' + f
if not os.path.isfile(newLoc):
try:
shutil.copy2(oldLoc, newLoc)
print 'File ' + f + ' copied.'
except IOError:
print 'file "' + f + '" already exists'
I am sorry I am being handful but I thought I had solved it.
In theory, there is no way to access your phone's internal memmory with a drive letter, because, Android connects as an MTP device, and not as a Mass Storage device. But, there are some weird solutions:
Root the phone and get a application which enables "Mass Storage" .
If you can not root and if(only if) both the computer are on the same network, run FTP server in you phone, and you get access for file copy by ftp.
But for you case I recommend adb- adb push C:\src /phone_destination is the best solution.You can google and easily find out way to do this in python.
Google offers adb-sync, which is also available in python. This allows backup/synchronization of files on android device to PC.
The following github repo provides instructions on how to setup the process ie: enable USB Debugging, etc... however I suggest installing 15 second adb installer as opposed to downloading/installing the massive Android SDK just to get adb.
adb-sync: https://github.com/google/adb-sync
15 Sec ADB installer: https://forum.xda-developers.com/showthread.php?t=2588979
A little late to answer, but I use SSH certs and crontab to run a ping command against my local IP and pipe that to an scp recursive copy. It will copy any changes over. No issue yet, and it's been running 4 years straight. I can't for the life of me find the command line that's running.
If I do:
adb install myAppRelease-2012-07-24_14-35-14.apk
When I try to reference the actual .apk file after it is installed
PackageManager pm = this.getPackageManager();
for (ApplicationInfo app : pm.getInstalledApplications(PackageManager.GET_META_DATA)) {
Log.d("PackageList", "package: " + app.packageName + ", sourceDir: " + app.sourceDir);
String appName = "myAppRelease"
if(app.packageName.contains("myApp")){
if(app.sourceDir.contains(appName)){
apkVersion = app.sourceDir.substring(app.sourceDir.indexOf(appName), app.sourceDir.indexOf(apk));
}
}
}
What I see is this:
07-24 14:46:40.190: D/PackageList(7421): package: myApp, sourceDir: /data/app/myApp-1.apk
What I expected to see is this:
07-24 14:46:40.190: D/PackageList(7421): package: myApp, sourceDir: /data/app/myAppRelease-2012-07-24_14-35-14.apk
It appears that it uses
android:label="myApp"
android:versionCode="1"
from the manifest file.
My question is, why doesn't it keep the original .apk filename? I am relying on the .apk filename to display version information for my app.
You can't rely on this, and it definitely doesn't use the version code for the number after the '-'. Especially with newer versions, apps can be moved to the SD card, or forward locked (aka, 'app encryption'), so the actual file on disk can be very different from the original file.
Use PackageManager to get version info, that is guaranteed to be correct and up to date. Change your build system to update the version code and maybe display it in an About dialog or similar so it is easy for users to report it.