feat: support cloning apps with split apks

* This is available since O, but no special logic is needed until Q.
This commit is contained in:
Peter Cai 2020-07-02 09:36:34 +08:00
parent 411487db24
commit 2911a1d3ae
No known key found for this signature in database
GPG key ID: 71F5FB4E4F3FD54F
3 changed files with 37 additions and 11 deletions

View file

@ -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();

View file

@ -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<Uri> 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);

View file

@ -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;