RollbackManager: Backup and restore split apks.

Previously RollbackManager would only backup and restore the base apk
for a package. This change fixes it to backup and restore split apks as
well.

Adds a new test for rollback of packages built with splits.

Also, switch to using resources to distinguish between v1 and v2 of an
application instead of meta-data, given we use resources anyway now to
distinguish between split versions.

Bug: 124848643
Test: atest RollbackTest, with new test for splits added.
Change-Id: Id1f32db7b728bdeda6c7223bc69a7e80ba569dda
This commit is contained in:
Richard Uhler
2019-02-25 12:11:05 +00:00
parent c704edbe44
commit ab009ea44c
15 changed files with 221 additions and 40 deletions

View File

@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
@@ -47,6 +48,7 @@ import android.util.SparseBooleanArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.Installer;
@@ -362,20 +364,24 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
File packageCode = RollbackStore.getPackageCode(data, info.getPackageName());
if (packageCode == null) {
File[] packageCodePaths = RollbackStore.getPackageCodePaths(
data, info.getPackageName());
if (packageCodePaths == null) {
sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
"Backup copy of package code inaccessible");
"Backup copy of package inaccessible");
return;
}
try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCode,
ParcelFileDescriptor.MODE_READ_ONLY)) {
final long token = Binder.clearCallingIdentity();
try {
session.write(packageCode.getName(), 0, packageCode.length(), fd);
} finally {
Binder.restoreCallingIdentity(token);
for (File packageCodePath : packageCodePaths) {
try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
ParcelFileDescriptor.MODE_READ_ONLY)) {
final long token = Binder.clearCallingIdentity();
try {
session.write(packageCodePath.getName(), 0, packageCodePath.length(),
fd);
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
parentSession.addChildSessionId(sessionId);
@@ -950,7 +956,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
try {
RollbackStore.backupPackageCode(data, packageName, pkgInfo.applicationInfo.sourceDir);
ApplicationInfo appInfo = pkgInfo.applicationInfo;
RollbackStore.backupPackageCodePath(data, packageName, appInfo.sourceDir);
if (!ArrayUtils.isEmpty(appInfo.splitSourceDirs)) {
for (String sourceDir : appInfo.splitSourceDirs) {
RollbackStore.backupPackageCodePath(data, packageName, sourceDir);
}
}
} catch (IOException e) {
Log.e(TAG, "Unable to copy package for rollback for " + packageName, e);
return false;

View File

@@ -234,9 +234,11 @@ class RollbackStore {
}
/**
* Creates a backup copy of the apk or apex for a package.
* Creates a backup copy of an apk or apex for a package.
* For packages containing splits, this method should be called for each
* of the package's split apks in addition to the base apk.
*/
static void backupPackageCode(RollbackData data, String packageName, String codePath)
static void backupPackageCodePath(RollbackData data, String packageName, String codePath)
throws IOException {
File sourceFile = new File(codePath);
File targetDir = new File(data.backupDir, packageName);
@@ -248,16 +250,16 @@ class RollbackStore {
}
/**
* Returns the apk or apex file backed up for the given package.
* Returns null if none found.
* Returns the apk or apex files backed up for the given package.
* Includes the base apk and any splits. Returns null if none found.
*/
static File getPackageCode(RollbackData data, String packageName) {
static File[] getPackageCodePaths(RollbackData data, String packageName) {
File targetDir = new File(data.backupDir, packageName);
File[] files = targetDir.listFiles();
if (files == null || files.length != 1) {
if (files == null || files.length == 0) {
return null;
}
return files[0];
return files;
}
/**

View File

@@ -21,6 +21,7 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
LOCAL_MANIFEST_FILE := TestApp/Av1.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/TestApp/res_v1
LOCAL_PACKAGE_NAME := RollbackTestAppAv1
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_AV1 := $(LOCAL_INSTALLED_MODULE)
@@ -32,6 +33,7 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
LOCAL_MANIFEST_FILE := TestApp/Av2.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/TestApp/res_v2
LOCAL_PACKAGE_NAME := RollbackTestAppAv2
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_AV2 := $(LOCAL_INSTALLED_MODULE)
@@ -43,6 +45,7 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
LOCAL_MANIFEST_FILE := TestApp/ACrashingV2.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/TestApp/res_v2
LOCAL_PACKAGE_NAME := RollbackTestAppACrashingV2
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_A_CRASHING_V2 := $(LOCAL_INSTALLED_MODULE)
@@ -54,6 +57,7 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
LOCAL_MANIFEST_FILE := TestApp/Bv1.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/TestApp/res_v1
LOCAL_PACKAGE_NAME := RollbackTestAppBv1
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_BV1 := $(LOCAL_INSTALLED_MODULE)
@@ -65,10 +69,39 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
LOCAL_MANIFEST_FILE := TestApp/Bv2.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/TestApp/res_v2
LOCAL_PACKAGE_NAME := RollbackTestAppBv2
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_BV2 := $(LOCAL_INSTALLED_MODULE)
# RollbackTestAppASplitV1.apk
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
LOCAL_MANIFEST_FILE := TestApp/Av1.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/TestApp/res_v1
LOCAL_PACKAGE_NAME := RollbackTestAppASplitV1
LOCAL_PACKAGE_SPLITS := anydpi
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_A_SPLIT_V1 := $(LOCAL_INSTALLED_MODULE)
ROLLBACK_TEST_APP_A_SPLIT_V1_SPLIT := $(installed_apk_splits)
# RollbackTestAppASplitV2.apk
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
LOCAL_MANIFEST_FILE := TestApp/Av2.xml
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/TestApp/res_v2
LOCAL_PACKAGE_NAME := RollbackTestAppASplitV2
LOCAL_PACKAGE_SPLITS := anydpi
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_A_SPLIT_V2 := $(LOCAL_INSTALLED_MODULE)
ROLLBACK_TEST_APP_A_SPLIT_V2_SPLIT := $(installed_apk_splits)
# RollbackTest
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, RollbackTest/src)
@@ -82,6 +115,10 @@ LOCAL_JAVA_RESOURCE_FILES := \
$(ROLLBACK_TEST_APP_A_CRASHING_V2) \
$(ROLLBACK_TEST_APP_BV1) \
$(ROLLBACK_TEST_APP_BV2) \
$(ROLLBACK_TEST_APP_A_SPLIT_V1) \
$(ROLLBACK_TEST_APP_A_SPLIT_V1_SPLIT) \
$(ROLLBACK_TEST_APP_A_SPLIT_V2) \
$(ROLLBACK_TEST_APP_A_SPLIT_V2_SPLIT) \
$(ROLLBACK_TEST_APEX_V1) \
$(ROLLBACK_TEST_APEX_V2)
LOCAL_MANIFEST_FILE := RollbackTest/AndroidManifest.xml
@@ -103,5 +140,9 @@ include $(BUILD_HOST_JAVA_LIBRARY)
ROLLBACK_TEST_APP_AV1 :=
ROLLBACK_TEST_APP_AV2 :=
ROLLBACK_TEST_APP_A_CRASHING_V2 :=
ROLLBACK_TEST_APP_A_SPLIT_V1 :=
ROLLBACK_TEST_APP_A_SPLIT_V1_SPLIT :=
ROLLBACK_TEST_APP_A_SPLIT_V2 :=
ROLLBACK_TEST_APP_A_SPLIT_V2_SPLIT :=
ROLLBACK_TEST_APP_BV1 :=
ROLLBACK_TEST_APP_BV2 :=

View File

@@ -441,6 +441,39 @@ public class RollbackTest {
}
}
/**
* Test rollback of apks involving splits.
*/
@Test
public void testRollbackWithSplits() throws Exception {
try {
RollbackTestUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS);
RollbackTestUtils.uninstall(TEST_APP_A);
RollbackTestUtils.installSplit(false,
"RollbackTestAppASplitV1.apk",
"RollbackTestAppASplitV1_anydpi.apk");
processUserData(TEST_APP_A);
RollbackTestUtils.installSplit(true,
"RollbackTestAppASplitV2.apk",
"RollbackTestAppASplitV2_anydpi.apk");
processUserData(TEST_APP_A);
RollbackManager rm = RollbackTestUtils.getRollbackManager();
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertNotNull(rollback);
RollbackTestUtils.rollback(rollback.getRollbackId());
processUserData(TEST_APP_A);
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
}
/**
* Test restrictions on rollback broadcast sender.
* A random app should not be able to send a ROLLBACK_COMMITTED broadcast.

View File

@@ -130,6 +130,19 @@ class RollbackTestUtils {
*/
static void install(String resourceName, boolean enableRollback)
throws InterruptedException, IOException {
installSplit(enableRollback, resourceName);
}
/**
* Installs the apk with the given name and its splits.
*
* @param enableRollback if rollback should be enabled.
* @param resourceNames names of class loader resources for the apk and
* its splits to install.
* @throws AssertionError if the installation fails.
*/
static void installSplit(boolean enableRollback, String... resourceNames)
throws InterruptedException, IOException {
Context context = InstrumentationRegistry.getContext();
PackageInstaller.Session session = null;
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
@@ -142,12 +155,14 @@ class RollbackTestUtils {
session = packageInstaller.openSession(sessionId);
ClassLoader loader = RollbackTest.class.getClassLoader();
try (OutputStream packageInSession = session.openWrite(resourceName, 0, -1);
InputStream is = loader.getResourceAsStream(resourceName);) {
byte[] buffer = new byte[4096];
int n;
while ((n = is.read(buffer)) >= 0) {
packageInSession.write(buffer, 0, n);
for (String resourceName : resourceNames) {
try (OutputStream packageInSession = session.openWrite(resourceName, 0, -1);
InputStream is = loader.getResourceAsStream(resourceName);) {
byte[] buffer = new byte[4096];
int n;
while ((n = is.read(buffer)) >= 0) {
packageInSession.write(buffer, 0, n);
}
}
}

View File

@@ -23,7 +23,6 @@
<uses-sdk android:minSdkVersion="19" />
<application android:label="Rollback Test App A v2">
<meta-data android:name="version" android:value="2" />
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity">

View File

@@ -23,7 +23,6 @@
<uses-sdk android:minSdkVersion="19" />
<application android:label="Rollback Test App A v1">
<meta-data android:name="version" android:value="1" />
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.tests.rollback.testapp.MainActivity">

View File

@@ -23,7 +23,6 @@
<uses-sdk android:minSdkVersion="19" />
<application android:label="Rollback Test App A v2">
<meta-data android:name="version" android:value="2" />
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.tests.rollback.testapp.MainActivity">

View File

@@ -23,7 +23,6 @@
<uses-sdk android:minSdkVersion="19" />
<application android:label="Rollback Test App B v1">
<meta-data android:name="version" android:value="1" />
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.tests.rollback.testapp.MainActivity">

View File

@@ -23,7 +23,6 @@
<uses-sdk android:minSdkVersion="19" />
<application android:label="Rollback Test App B v2">
<meta-data android:name="version" android:value="2" />
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.tests.rollback.testapp.MainActivity">

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<integer name="split_version">1</integer>
</resources>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<integer name="app_version">1</integer>
<integer name="split_version">0</integer>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<integer name="split_version">2</integer>
</resources>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<integer name="app_version">2</integer>
<integer name="split_version">0</integer>
</resources>

View File

@@ -19,9 +19,7 @@ package com.android.tests.rollback.testapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.content.res.Resources;
import java.io.File;
import java.io.FileNotFoundException;
@@ -35,6 +33,8 @@ import java.util.Scanner;
*/
public class ProcessUserData extends BroadcastReceiver {
private static final String TAG = "RollbackTestApp";
/**
* Exception thrown in case of issue with user data.
*/
@@ -66,14 +66,19 @@ public class ProcessUserData extends BroadcastReceiver {
* @throws UserDataException in case of problems with app user data.
*/
public void processUserData(Context context) throws UserDataException {
int appVersion = 0;
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
context.getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = appInfo.metaData;
appVersion = bundle.getInt("version");
} catch (PackageManager.NameNotFoundException e) {
throw new UserDataException("Unable to get app version info", e);
Resources res = context.getResources();
String packageName = context.getPackageName();
int appVersionId = res.getIdentifier("app_version", "integer", packageName);
int appVersion = res.getInteger(appVersionId);
int splitVersionId = res.getIdentifier("split_version", "integer", packageName);
int splitVersion = res.getInteger(splitVersionId);
// Make sure the app version and split versions are compatible.
if (appVersion != splitVersion) {
throw new UserDataException("Split version " + splitVersion
+ " does not match app version " + appVersion);
}
// Read the version of the app's user data and ensure it is compatible