diff --git a/app/src/main/java/net/typeblog/shelter/services/ShelterService.java b/app/src/main/java/net/typeblog/shelter/services/ShelterService.java index 2b3034d..2d6b3de 100644 --- a/app/src/main/java/net/typeblog/shelter/services/ShelterService.java +++ b/app/src/main/java/net/typeblog/shelter/services/ShelterService.java @@ -9,6 +9,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -121,6 +122,8 @@ public class ShelterService extends Service { intent.setComponent(new ComponentName(ShelterService.this, DummyActivity.class)); intent.putExtra("package", app.getPackageName()); intent.putExtra("apk", app.getSourceDir()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + intent.putExtra("split_apks", app.getSplitApks()); // Send the callback to the DummyActivity Bundle callbackExtra = new Bundle(); diff --git a/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java b/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java index 7eda498..95b9ad0 100644 --- a/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java +++ b/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java @@ -43,6 +43,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -294,7 +295,12 @@ public class DummyActivity extends Activity { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { try { - actionInstallPackageQ(uri); + // For Q, since we use the more "manual" method of installation, + // we have to also pass the split APKs ("Configuration APKs" as Google calls it) + // Although these are available since API 26, we don't need to + // take care of them for versions before Q since we don't actually + // install the APKs before Q. + actionInstallPackageQ(uri, getIntent().getStringArrayExtra("split_apks")); } catch (IOException e) { throw new RuntimeException(e); } @@ -315,7 +321,7 @@ public class DummyActivity extends Activity { // We have to switch to using PackageInstaller for the job, which isn't quite // as elegant because now we really need to read the entire apk and write to it // Keep this case only for Q for now. - private void actionInstallPackageQ(Uri uri) throws IOException { + private void actionInstallPackageQ(Uri uri, String[] split_apks) throws IOException { PackageInstaller pi = getPackageManager().getPackageInstaller(); PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); @@ -325,7 +331,7 @@ public class DummyActivity extends Activity { pi.registerSessionCallback(new InstallationProgressListener(this, pi, sessionId)); PackageInstaller.Session session = pi.openSession(sessionId); - doInstallPackageQ(uri, session, () -> { + doInstallPackageQ(uri, split_apks, session, () -> { // We have finished piping the streams, show the progress as 10% session.setStagingProgress(0.1f); @@ -341,15 +347,25 @@ public class DummyActivity extends Activity { // The background part of the installation process on Q (reading APKs etc) // that must be executed on another thread // Put them in background to avoid stalling the UI thread - private void doInstallPackageQ(Uri uri, PackageInstaller.Session session, Runnable callback) { - new Thread(() -> { - try (InputStream is = getContentResolver().openInputStream(uri); - OutputStream os = session.openWrite(UUID.randomUUID().toString(), 0, is.available()) - ) { - Utility.pipe(is, os); - session.fsync(os); - } catch (IOException e) { + private void doInstallPackageQ(Uri baseUri, String[] split_apks, PackageInstaller.Session session, Runnable callback) { + ArrayList uris = new ArrayList<>(); + uris.add(baseUri); + if (split_apks != null && split_apks.length > 0) { + for (String apk : split_apks) { + uris.add(Uri.fromFile(new File(apk))); + } + } + new Thread(() -> { + for (Uri uri : uris) { + try (InputStream is = getContentResolver().openInputStream(uri); + OutputStream os = session.openWrite(UUID.randomUUID().toString(), 0, is.available()) + ) { + Utility.pipe(is, os); + session.fsync(os); + } catch (IOException e) { + + } } runOnUiThread(callback); diff --git a/app/src/main/java/net/typeblog/shelter/util/ApplicationInfoWrapper.java b/app/src/main/java/net/typeblog/shelter/util/ApplicationInfoWrapper.java index 37b5308..f1ec557 100644 --- a/app/src/main/java/net/typeblog/shelter/util/ApplicationInfoWrapper.java +++ b/app/src/main/java/net/typeblog/shelter/util/ApplicationInfoWrapper.java @@ -1,7 +1,9 @@ package net.typeblog.shelter.util; +import android.annotation.TargetApi; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -55,6 +57,11 @@ public class ApplicationInfoWrapper implements Parcelable { return mInfo.sourceDir; } + @TargetApi(Build.VERSION_CODES.O) + public String[] getSplitApks() { + return mInfo.splitSourceDirs; + } + // NOTE: This does not relate to the "freezing" feature in Shelter public boolean getEnabled() { return mInfo.enabled;