Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Commit

Permalink
Introduce uses-native-library tag
Browse files Browse the repository at this point in the history
Since Android 7.0, partners were able to export some of their native
shared libraries to apps. So far, the native libraries were provided to
all apps regardless of whether they are actually needed or not. Even
worse, it was impossible to prevent the installation of an app to the
device where the some (or all) of the required native libs don't exist;
the apps had to manually handle the case, which sometimes impossible
when the dependency is so fundamental.

This change introduces a new tag <uses-native-library> to the app
manifest. Similary to the existing <uses-library> tag which is for
(java) shared libraries, the new tag is used to describe the depedencies
to the native shared libraries.

Apps targeting Android S or higher are required to use the tag to import
the native shared libraries. Libraries that are not depended on won't be
available to the app even if the libraries are listed in the
public.libraries*.txt files. Furthermore, when the dependency can't be
satisfied for an app, the package manager refejects installing the app.

The dependency can be optional using the `android:required` attribute.
When it is set to true, the absence of the lib on the device doesn't
prevent the app from being installed. However, the app has to gracefully
deal with the absence.

The changed behavior only affects apps targeting S or higher. Existing
apps are unaffected; they still get all the public native libraries
regardless of whether they have <uses-native-library> tags or not; the
tags are simply ignored.

This is the first version of the implementation and therefore needs
further refinements. The followings are two major TODOs.

1) The native shared lib dependencies of the java shared libraries
are not enforced. For example, if an app depends on a java shared
library foo and foo depends on some native shared libraries, the
classloader where code from foo is loaded still gets all native shared
libraries. This should be fixed.

2) New APIs should be added. SharedLibraryInfo should be extended to
represent native shared libraries. The meaning of
ApplicationInfo.sharedLibraryFiles should be revised. Finally, the new
tag should be made public.

Bug: 142191088
Test: atest CtsUsesNativeLibraryTest
Change-Id: Iceb038aa86872d23e9faf582ae91b1fdcaf5c64c
  • Loading branch information
jiyongp committed Jul 20, 2020
1 parent 43c87db commit 6a5b8b1
Show file tree
Hide file tree
Showing 16 changed files with 343 additions and 26 deletions.
28 changes: 20 additions & 8 deletions core/java/android/app/ApplicationLoaders.java
Expand Up @@ -48,17 +48,18 @@ ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
ClassLoader parent, String classLoaderName) {
return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled,
librarySearchPath, libraryPermittedPath, parent, classLoaderName,
null);
null, null);
}

ClassLoader getClassLoaderWithSharedLibraries(
String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent, String classLoaderName,
List<ClassLoader> sharedLibraries) {
List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) {
// For normal usage the cache key used is the same as the zip path.
return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries);
libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
nativeSharedLibraries);
}

/**
Expand All @@ -77,14 +78,22 @@ ClassLoader getSharedLibraryClassLoaderWithSharedLibraries(String zip, int targe
return loader;
}

// TODO(b/142191088): allow (Java) shared libraries to have <uses-native-library>
// Until that is supported, assume that all native shared libraries are used.
// "ALL" is a magic string that libnativeloader uses to unconditionally add all available
// native shared libraries to the classloader.
List<String> nativeSharedLibraries = new ArrayList<>();
nativeSharedLibraries.add("ALL");
return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled,
librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries);
librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries,
nativeSharedLibraries);
}

private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent, String cacheKey,
String classLoaderName, List<ClassLoader> sharedLibraries) {
String classLoaderName, List<ClassLoader> sharedLibraries,
List<String> nativeSharedLibraries) {
/*
* This is the parent we use if they pass "null" in. In theory
* this should be the "system" class loader; in practice we
Expand Down Expand Up @@ -113,7 +122,8 @@ private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isB

ClassLoader classloader = ClassLoaderFactory.createClassLoader(
zip, librarySearchPath, libraryPermittedPath, parent,
targetSdkVersion, isBundled, classLoaderName, sharedLibraries);
targetSdkVersion, isBundled, classLoaderName, sharedLibraries,
nativeSharedLibraries);

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

Expand Down Expand Up @@ -185,7 +195,8 @@ private void createAndCacheNonBootclasspathSystemClassLoader(SharedLibraryInfo l
// assume cached libraries work with current sdk since they are built-in
ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/,
null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/,
null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/);
null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/,
null /* nativeSharedLibraries */);

if (classLoader == null) {
// bad configuration or break in classloading code
Expand Down Expand Up @@ -255,7 +266,8 @@ public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String l
// The cache key is passed separately to enable the stub WebView to be cached under the
// stub's APK path, when the actual package path is the donor APK.
return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null,
cacheKey, null /* classLoaderName */, null /* sharedLibraries */);
cacheKey, null /* classLoaderName */, null /* sharedLibraries */,
null /* nativeSharedLibraries */);
}

/**
Expand Down
23 changes: 22 additions & 1 deletion core/java/android/app/LoadedApk.java
Expand Up @@ -412,6 +412,12 @@ private static void appendSharedLibrariesLibPathsIfNeeded(
return;
}
for (SharedLibraryInfo lib : sharedLibraries) {
if (lib.isNative()) {
// Native shared lib doesn't contribute to the native lib search path. Its name is
// sent to libnativeloader and then the native shared lib is exported from the
// default linker namespace.
continue;
}
List<String> paths = lib.getAllCodePaths();
outSeenPaths.addAll(paths);
for (String path : paths) {
Expand Down Expand Up @@ -696,6 +702,12 @@ private List<ClassLoader> createSharedLibrariesLoaders(List<SharedLibraryInfo> s
}
List<ClassLoader> loaders = new ArrayList<>();
for (SharedLibraryInfo info : sharedLibraries) {
if (info.isNative()) {
// Native shared lib doesn't contribute to the native lib search path. Its name is
// sent to libnativeloader and then the native shared lib is exported from the
// default linker namespace.
continue;
}
loaders.add(createSharedLibraryLoader(
info, isBundledApp, librarySearchPath, libraryPermittedPath));
}
Expand Down Expand Up @@ -898,10 +910,19 @@ private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
mApplicationInfo.sharedLibraryInfos, isBundledApp, librarySearchPath,
libraryPermittedPath);

List<String> nativeSharedLibraries = new ArrayList<>();
if (mApplicationInfo.sharedLibraryInfos != null) {
for (SharedLibraryInfo info : mApplicationInfo.sharedLibraryInfos) {
if (info.isNative()) {
nativeSharedLibraries.add(info.getName());
}
}
}

mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader,
mApplicationInfo.classLoaderName, sharedLibraries);
mApplicationInfo.classLoaderName, sharedLibraries, nativeSharedLibraries);
mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);

setThreadPolicy(oldPolicy);
Expand Down
25 changes: 24 additions & 1 deletion core/java/android/content/pm/SharedLibraryInfo.java
Expand Up @@ -79,6 +79,7 @@ public final class SharedLibraryInfo implements Parcelable {

private final long mVersion;
private final @Type int mType;
private final boolean mIsNative;
private final VersionedPackage mDeclaringPackage;
private final List<VersionedPackage> mDependentPackages;
private List<SharedLibraryInfo> mDependencies;
Expand All @@ -93,13 +94,14 @@ public final class SharedLibraryInfo implements Parcelable {
* @param type The lib type.
* @param declaringPackage The package that declares the library.
* @param dependentPackages The packages that depend on the library.
* @param isNative indicate if this shared lib is a native lib or not (i.e. java)
*
* @hide
*/
public SharedLibraryInfo(String path, String packageName, List<String> codePaths,
String name, long version, int type,
VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages,
List<SharedLibraryInfo> dependencies) {
List<SharedLibraryInfo> dependencies, boolean isNative) {
mPath = path;
mPackageName = packageName;
mCodePaths = codePaths;
Expand All @@ -109,6 +111,16 @@ public SharedLibraryInfo(String path, String packageName, List<String> codePaths
mDeclaringPackage = declaringPackage;
mDependentPackages = dependentPackages;
mDependencies = dependencies;
mIsNative = isNative;
}

/** @hide */
public SharedLibraryInfo(String path, String packageName, List<String> codePaths,
String name, long version, int type,
VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages,
List<SharedLibraryInfo> dependencies) {
this(path, packageName, codePaths, name, version, type, declaringPackage, dependentPackages,
dependencies, false /* isNative */);
}

private SharedLibraryInfo(Parcel parcel) {
Expand All @@ -125,6 +137,7 @@ private SharedLibraryInfo(Parcel parcel) {
mDeclaringPackage = parcel.readParcelable(null);
mDependentPackages = parcel.readArrayList(null);
mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
mIsNative = parcel.readBoolean();
}

/**
Expand All @@ -136,6 +149,15 @@ private SharedLibraryInfo(Parcel parcel) {
return mType;
}

/**
* Tells whether this library is a native shared library or not.
*
* @hide
*/
public boolean isNative() {
return mIsNative;
}

/**
* Gets the library name an app defines in its manifest
* to depend on the library.
Expand Down Expand Up @@ -320,6 +342,7 @@ public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mDeclaringPackage, flags);
parcel.writeList(mDependentPackages);
parcel.writeTypedList(mDependencies);
parcel.writeBoolean(mIsNative);
}

private static String typeToString(int type) {
Expand Down
6 changes: 6 additions & 0 deletions core/java/android/content/pm/parsing/ParsingPackage.java
Expand Up @@ -92,6 +92,10 @@ public interface ParsingPackage extends ParsingPackageRead {

ParsingPackage addUsesOptionalLibrary(String libraryName);

ParsingPackage addUsesNativeLibrary(String libraryName);

ParsingPackage addUsesOptionalNativeLibrary(String libraryName);

ParsingPackage addUsesStaticLibrary(String libraryName);

ParsingPackage addUsesStaticLibraryCertDigests(String[] certSha256Digests);
Expand Down Expand Up @@ -219,6 +223,8 @@ ParsingPackage asSplit(

ParsingPackage removeUsesOptionalLibrary(String libraryName);

ParsingPackage removeUsesOptionalNativeLibrary(String libraryName);

ParsingPackage setAnyDensity(int anyDensity);

ParsingPackage setAppComponentFactory(String appComponentFactory);
Expand Down
44 changes: 44 additions & 0 deletions core/java/android/content/pm/parsing/ParsingPackageImpl.java
Expand Up @@ -184,6 +184,13 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable {
@DataClass.ParcelWith(ForInternedStringList.class)
protected List<String> usesOptionalLibraries = emptyList();

@NonNull
@DataClass.ParcelWith(ForInternedStringList.class)
protected List<String> usesNativeLibraries = emptyList();
@NonNull
@DataClass.ParcelWith(ForInternedStringList.class)
protected List<String> usesOptionalNativeLibraries = emptyList();

@NonNull
@DataClass.ParcelWith(ForInternedStringList.class)
private List<String> usesStaticLibraries = emptyList();
Expand Down Expand Up @@ -668,6 +675,27 @@ public ParsingPackageImpl removeUsesOptionalLibrary(String libraryName) {
return this;
}

@Override
public final ParsingPackageImpl addUsesOptionalNativeLibrary(String libraryName) {
this.usesOptionalNativeLibraries = CollectionUtils.add(this.usesOptionalNativeLibraries,
TextUtils.safeIntern(libraryName));
return this;
}

@Override
public final ParsingPackageImpl addUsesNativeLibrary(String libraryName) {
this.usesNativeLibraries = CollectionUtils.add(this.usesNativeLibraries,
TextUtils.safeIntern(libraryName));
return this;
}


@Override public ParsingPackageImpl removeUsesOptionalNativeLibrary(String libraryName) {
this.usesOptionalNativeLibraries = CollectionUtils.remove(this.usesOptionalNativeLibraries,
libraryName);
return this;
}

@Override
public ParsingPackageImpl addUsesStaticLibrary(String libraryName) {
this.usesStaticLibraries = CollectionUtils.add(this.usesStaticLibraries,
Expand Down Expand Up @@ -982,6 +1010,8 @@ public void writeToParcel(Parcel dest, int flags) {
sForInternedStringList.parcel(this.libraryNames, dest, flags);
sForInternedStringList.parcel(this.usesLibraries, dest, flags);
sForInternedStringList.parcel(this.usesOptionalLibraries, dest, flags);
sForInternedStringList.parcel(this.usesNativeLibraries, dest, flags);
sForInternedStringList.parcel(this.usesOptionalNativeLibraries, dest, flags);
sForInternedStringList.parcel(this.usesStaticLibraries, dest, flags);
dest.writeLongArray(this.usesStaticLibrariesVersions);

Expand Down Expand Up @@ -1144,6 +1174,8 @@ public ParsingPackageImpl(Parcel in) {
this.libraryNames = sForInternedStringList.unparcel(in);
this.usesLibraries = sForInternedStringList.unparcel(in);
this.usesOptionalLibraries = sForInternedStringList.unparcel(in);
this.usesNativeLibraries = sForInternedStringList.unparcel(in);
this.usesOptionalNativeLibraries = sForInternedStringList.unparcel(in);
this.usesStaticLibraries = sForInternedStringList.unparcel(in);
this.usesStaticLibrariesVersions = in.createLongArray();

Expand Down Expand Up @@ -1415,6 +1447,18 @@ public List<String> getUsesOptionalLibraries() {
return usesOptionalLibraries;
}

@NonNull
@Override
public List<String> getUsesNativeLibraries() {
return usesNativeLibraries;
}

@NonNull
@Override
public List<String> getUsesOptionalNativeLibraries() {
return usesOptionalNativeLibraries;
}

@NonNull
@Override
public List<String> getUsesStaticLibraries() {
Expand Down
13 changes: 13 additions & 0 deletions core/java/android/content/pm/parsing/ParsingPackageRead.java
Expand Up @@ -230,6 +230,19 @@ public interface ParsingPackageRead extends Parcelable {
@NonNull
List<String> getUsesOptionalLibraries();

/** @see R.styleabele#AndroidManifestUsesNativeLibrary */
@NonNull
List<String> getUsesNativeLibraries();

/**
* Like {@link #getUsesNativeLibraries()}, but marked optional by setting
* {@link R.styleable#AndroidManifestUsesNativeLibrary_required} to false . Application is
* expected to handle absence manually.
* @see R.styleable#AndroidManifestUsesNativeLibrary
*/
@NonNull
List<String> getUsesOptionalNativeLibraries();

/**
* TODO(b/135203078): Move static library stuff to an inner data class
* @see R.styleable#AndroidManifestUsesStaticLibrary
Expand Down
35 changes: 35 additions & 0 deletions core/java/android/content/pm/parsing/ParsingPackageUtils.java
Expand Up @@ -701,6 +701,8 @@ private ParseResult parseSplitBaseAppChildTags(ParseInput input, String tag, Par
return parseUsesStaticLibrary(input, pkg, res, parser);
case "uses-library":
return parseUsesLibrary(input, pkg, res, parser);
case "uses-native-library":
return parseUsesNativeLibrary(input, pkg, res, parser);
case "uses-package":
// Dependencies for app installers; we don't currently try to
// enforce this.
Expand Down Expand Up @@ -2017,6 +2019,8 @@ private ParseResult parseBaseAppChildTag(ParseInput input, String tag, ParsingPa
return parseUsesStaticLibrary(input, pkg, res, parser);
case "uses-library":
return parseUsesLibrary(input, pkg, res, parser);
case "uses-native-library":
return parseUsesNativeLibrary(input, pkg, res, parser);
case "processes":
return parseProcesses(input, pkg, res, parser, mSeparateProcesses, flags);
case "uses-package":
Expand Down Expand Up @@ -2177,6 +2181,37 @@ private static ParseResult<ParsingPackage> parseUsesLibrary(ParseInput input,
}
}

@NonNull
private static ParseResult<ParsingPackage> parseUsesNativeLibrary(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesNativeLibrary);
try {
// Note: don't allow this value to be a reference to a resource
// that may change.
String lname = sa.getNonResourceString(
R.styleable.AndroidManifestUsesNativeLibrary_name);
boolean req = sa.getBoolean(R.styleable.AndroidManifestUsesNativeLibrary_required,
true);

if (lname != null) {
if (req) {
// Upgrade to treat as stronger constraint
pkg.addUsesNativeLibrary(lname)
.removeUsesOptionalNativeLibrary(lname);
} else {
// Ignore if someone already defined as required
if (!ArrayUtils.contains(pkg.getUsesNativeLibraries(), lname)) {
pkg.addUsesOptionalNativeLibrary(lname);
}
}
}

return input.success(pkg);
} finally {
sa.recycle();
}
}

@NonNull
private static ParseResult<ParsingPackage> parseProcesses(ParseInput input, ParsingPackage pkg,
Resources res, XmlResourceParser parser, String[] separateProcesses, int flags)
Expand Down

0 comments on commit 6a5b8b1

Please sign in to comment.