Add <extension-sdk> manifest tag inside <uses-sdk>

This allows apps to specify the minimum versions they need
of extension sdks, and fails install if they aren't met.

There is additional work required to prevent local rollbacks
from triggering a downgrade of SDK versions after an install.

Bug: 137191822
Test: atest PackageParserTest
Exempt-From-Owner-Approval: PS4 was approved
Change-Id: If61ae6c67ceb752bec6876006a29e52b996901e7
This commit is contained in:
Anton Hansson
2020-01-09 10:25:23 +00:00
parent b2aafcb399
commit b2f709db1d
15 changed files with 291 additions and 3 deletions

View File

@@ -439,8 +439,8 @@ java_library {
srcs: [":framework-non-updatable-sources"],
libs: [
"framework-appsearch-stubs",
// TODO(b/146167933): Use framework-statsd-stubs
"framework-statsd",
"framework-sdkextensions-stubs-systemapi",
"framework-statsd", // TODO(b/146167933): Use framework-statsd-stubs
"framework-permission-stubs",
"framework-wifi-stubs",
"ike-stubs",

View File

@@ -242,8 +242,10 @@ package android {
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
field public static final int isVrOnly = 16844152; // 0x1010578
field public static final int minExtensionVersion = 16844306; // 0x1010612
field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
field public static final int sdkVersion = 16844305; // 0x1010611
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
}

View File

@@ -65,6 +65,7 @@ import android.os.FileUtils;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.ext.SdkExtensions;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1615,11 +1616,72 @@ public class ApkParseUtils {
);
}
int type;
final int innerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
if (parser.getName().equals("extension-sdk")) {
final ParseResult result =
parseExtensionSdk(parseInput, parsingPackage, res, parser);
if (!result.isSuccess()) {
return result;
}
} else {
Slog.w(TAG, "Unknown element under <uses-sdk>: " + parser.getName()
+ " at " + parsingPackage.getBaseCodePath() + " "
+ parser.getPositionDescription());
}
XmlUtils.skipCurrentTag(parser);
}
parsingPackage.setMinSdkVersion(minSdkVersion)
.setTargetSdkVersion(targetSdkVersion);
}
return parseInput.success(parsingPackage);
}
XmlUtils.skipCurrentTag(parser);
private static ParseResult parseExtensionSdk(
ParseInput parseInput,
ParsingPackage parsingPackage,
Resources res,
XmlResourceParser parser
) throws IOException, XmlPullParserException {
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestExtensionSdk);
int sdkVersion = sa.getInt(
com.android.internal.R.styleable.AndroidManifestExtensionSdk_sdkVersion, -1);
int minVersion = sa.getInt(
com.android.internal.R.styleable.AndroidManifestExtensionSdk_minExtensionVersion,
-1);
sa.recycle();
if (sdkVersion < 0) {
return parseInput.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"<extension-sdk> must specify an sdkVersion >= 0");
}
if (minVersion < 0) {
return parseInput.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"<extension-sdk> must specify minExtensionVersion >= 0");
}
try {
int version = SdkExtensions.getExtensionVersion(sdkVersion);
if (version < minVersion) {
return parseInput.error(
PackageManager.INSTALL_FAILED_OLDER_SDK,
"Package requires " + sdkVersion + " extension version " + minVersion
+ " which exceeds device version " + version);
}
} catch (RuntimeException e) {
return parseInput.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Specified sdkVersion " + sdkVersion + " is not valid");
}
return parseInput.success(parsingPackage);
}

View File

@@ -2005,6 +2005,15 @@
<attr name="maxSdkVersion" />
</declare-styleable>
<!-- The <code>extension-sdk</code> tag is a child of the <uses-sdk> tag,
and specifies required extension sdk features. -->
<declare-styleable name="AndroidManifestExtensionSdk">
<!-- The extension SDK version that this tag refers to. -->
<attr name="sdkVersion" format="integer" />
<!-- The minimum version of the extension SDK this application requires.-->
<attr name="minExtensionVersion" format="integer" />
</declare-styleable>
<!-- The <code>library</code> tag declares that this apk is providing itself
as a shared library for other applications to use. It can only be used
with apks that are built in to the system image. Other apks can link to

View File

@@ -3008,6 +3008,10 @@
<public name="supportsInlineSuggestions" />
<public name="crossProfile" />
<public name="canTakeScreenshot"/>
<!-- @hide @SystemApi -->
<public name="sdkVersion" />
<!-- @hide @SystemApi -->
<public name="minExtensionVersion" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">

View File

@@ -84,6 +84,11 @@ java_genrule {
":FrameworksCoreTests_install_split_feature_a",
":FrameworksCoreTests_install_use_perm_good",
":FrameworksCoreTests_install_uses_feature",
":FrameworksCoreTests_install_uses_sdk_0",
":FrameworksCoreTests_install_uses_sdk_q0",
":FrameworksCoreTests_install_uses_sdk_r",
":FrameworksCoreTests_install_uses_sdk_r0",
":FrameworksCoreTests_install_uses_sdk_r5",
":FrameworksCoreTests_install_verifier_bad",
":FrameworksCoreTests_install_verifier_good",
":FrameworksCoreTests_keyset_permdef_sa_unone",

View File

@@ -0,0 +1,39 @@
android_test_helper_app {
name: "FrameworksCoreTests_install_uses_sdk_r0",
defaults: ["FrameworksCoreTests_apks_defaults"],
manifest: "AndroidManifest-r0.xml",
srcs: ["**/*.java"],
}
android_test_helper_app {
name: "FrameworksCoreTests_install_uses_sdk_r5",
defaults: ["FrameworksCoreTests_apks_defaults"],
manifest: "AndroidManifest-r5.xml",
srcs: ["**/*.java"],
}
android_test_helper_app {
name: "FrameworksCoreTests_install_uses_sdk_q0",
defaults: ["FrameworksCoreTests_apks_defaults"],
manifest: "AndroidManifest-q0.xml",
srcs: ["**/*.java"],
}
android_test_helper_app {
name: "FrameworksCoreTests_install_uses_sdk_r",
defaults: ["FrameworksCoreTests_apks_defaults"],
manifest: "AndroidManifest-r.xml",
srcs: ["**/*.java"],
}
android_test_helper_app {
name: "FrameworksCoreTests_install_uses_sdk_0",
defaults: ["FrameworksCoreTests_apks_defaults"],
manifest: "AndroidManifest-0.xml",
srcs: ["**/*.java"],
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.install_uses_sdk">
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This is invalid, because there is no sdk version specified -->
<extension-sdk android:minExtensionVersion="5" />
</uses-sdk>
<application>
</application>
</manifest>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.install_uses_sdk">
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This fails because 29 doesn't have an extension sdk -->
<extension-sdk android:sdkVersion="29" android:minExtensionVersion="0" />
</uses-sdk>
<application>
</application>
</manifest>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.install_uses_sdk">
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This is invalid, because there is no minimum extension version specified -->
<extension-sdk android:sdkVersion="10000" />
</uses-sdk>
<application>
</application>
</manifest>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.install_uses_sdk">
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<extension-sdk android:sdkVersion="10000" android:minExtensionVersion="0" />
</uses-sdk>
<application>
</application>
</manifest>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.install_uses_sdk">
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This will fail to install, because minExtensionVersion is not met -->
<extension-sdk android:sdkVersion="10000" android:minExtensionVersion="5" />
</uses-sdk>
<application>
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Just need this dummy file to have something to build. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="dummy">dummy</string>
</resources>

View File

@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.apex.ApexInfo;
import android.content.Context;
@@ -486,4 +487,34 @@ public class PackageParserTest {
assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0);
assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
}
@Test
public void testUsesSdk() throws Exception {
parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x);
try {
parsePackage("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5, x -> x);
fail("Expected parsing exception due to incompatible extension SDK version");
} catch (PackageParser.PackageParserException expected) {
assertEquals(PackageManager.INSTALL_FAILED_OLDER_SDK, expected.error);
}
try {
parsePackage("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0, x -> x);
fail("Expected parsing exception due to non-existent extension SDK");
} catch (PackageParser.PackageParserException expected) {
assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
}
try {
parsePackage("install_uses_sdk.apk_r", R.raw.install_uses_sdk_r, x -> x);
fail("Expected parsing exception due to unspecified extension SDK version");
} catch (PackageParser.PackageParserException expected) {
assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
}
try {
parsePackage("install_uses_sdk.apk_0", R.raw.install_uses_sdk_0, x -> x);
fail("Expected parsing exception due to unspecified extension SDK");
} catch (PackageParser.PackageParserException expected) {
assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
}
}
}

View File

@@ -349,6 +349,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
}
return true;
});
manifest_action["uses-sdk"]["extension-sdk"];
// Instrumentation actions.
manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName);