How to use “libsu” library (or adb) to install split APK files on Android Q?

自作多情 提交于 2019-12-11 01:07:21

问题


Background

Using root, I know that for a single APK file, we can use the "libsu" library (here) to install as such:

val installResult = Shell.su("pm install -t \"$filePath\"").exec()

And if that failed (fails on new Android versions, not sure from which), as such (written about this here):

val installResult = Shell.su("cat \"$filePath\" | pm install -t -S ${apkSource.fileSize}").exec()

I also know that things got quite messy when it comes to installing split APK files (as shown here). First you need to create a session, using the "pm install-create" command:

var sessionId: Int? = null
run {
    val sessionIdResult =
            Shell.su("pm install-create -r -t").exec().out
    val sessionIdPattern = Pattern.compile("(\\d+)")
    val sessionIdMatcher = sessionIdPattern.matcher(sessionIdResult[0])
    sessionIdMatcher.find()
    sessionId = Integer.parseInt(sessionIdMatcher.group(1)!!)
    Log.d("AppLog", "sessionId:$sessionId")
}

Then you have to "push" each of the APK files, as such:

for (apkSource in fileInfoList) {
    val filePath = File(apkSource.parentFilePath, apkSource.fileName).absolutePath
    Log.d("AppLog", "installing APK : $filePath ${apkSource.fileSize} ")
    val result = Shell.su("pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\" \"$filePath\"").exec()
    Log.d("AppLog", "success pushing apk:${apkSource.fileName} ? ${result.isSuccess}")
}

And then you commit the changes using pm install-commit :

val installResult = Shell.su("pm install-commit $sessionId").exec()

Docs about it all:

  install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]
       [-p INHERIT_PACKAGE] [--install-location 0/1/2]
       [--install-reason 0/1/2/3/4] [--originating-uri URI]
       [--referrer URI] [--abi ABI_NAME] [--force-sdk]
       [--preload] [--instantapp] [--full] [--dont-kill]
       [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]
       [--multi-package] [--staged]
    Like "install", but starts an install session.  Use "install-write"
    to push data into the session, and "install-commit" to finish.

  install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]
    Write an apk into the given install session.  If the path is '-', data
    will be read from stdin.  Options are:
      -S: size in bytes of package, required for stdin

  install-commit SESSION_ID
    Commit the given active install session, installing the app.

The problem

This all worked fine till Android P, but for some reason, it failed on Q beta 6, showing me this error:

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/split/base.apk, context u:r:system_server:s0)
Error: Unable to open file: /storage/emulated/0/Download/split/base.apk
Consider using a file under /data/local/tmp/

What I've tried

This is similar to the case I've found for the single APK , here, so I thought that maybe a similar solution can be applied here too:

val result = Shell.su("cat $filePath | pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\" \"$filePath\"").exec()

This still worked only on Android P and below.

So seeing that the original code I looked at worked, it uses an InputStream, which as the docs imply, is possible. Here's what they had:

while (apkSource.nextApk())
     ensureCommandSucceeded(Root.exec(String.format("pm install-write -S %d %d \"%s\"", apkSource.getApkLength(), sessionId, apkSource.getApkName()), apkSource.openApkInputStream()));

So what I tried is as such:

val result = Shell.su("pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\" -")
        .add(SuFileInputStream(filePath)).exec()

Sadly this also didn't work.

The question

I know I could just copy the same code, but is there still a way to use the library instead (because it will be shorter and more elegant)? If so, how could I do it?


回答1:


It's messy, but try this code. It is using SuFileInputStream to read the apk file contents, which it then pipes into the install-write command. This should in theory fix the problem.

                // getting session id
                val createSessionResult = Shell.su("pm install-create -S $size").
                val sessionIdRegex = "\\[([0-9]+)]".toRegex()
                var sessionId: Int? = null
                for (line in createSessionResult.out) {
                    val result = sessionIdRegex.find(line)?.groupValues?.get(1)?.toInt()
                    if (result != null) {
                        sessionId = result
                        break
                    }
                }

                // writing apks, you might want to extract this to another function
                val writeShellInStream = PipedInputStream()
                PipedOutputStream(writeShellInStream).use { writeShellInOutputStream ->
                    PrintWriter(writeShellInOutputStream).use { writeShellInWriter ->
                        writeShellInWriter.println("pm install-write -S $size $sessionId base") // eventually replace base with split apk name
                        writeShellInWriter.flush()

                        Shell.su(writeShellInStream).submit { writeResult ->
                            if (writeResult.isSuccess) {
                                Shell.su("pm install-commit $sessionId").submit { commitResult ->
                                    // commitResult.isSuccess to check if worked
                                }
                            }
                        }
                        apkInputStream.copyTo(writeShellInOutputStream)
                        writeShellInWriter.println()
                    }
                }

Edit: You might want to try the command "cat [your apk file] | pm install-write -S [size] [sessionId] [base / split apk name]" first if you don't need to install from stream. If cat doesn't work, try "dd if=[apk file]" instead.



来源:https://stackoverflow.com/questions/57468292/how-to-use-libsu-library-or-adb-to-install-split-apk-files-on-android-q

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!