Adds queries tag to manifest

This change adds support for a new <queries> tag as a child of the
<manifest> tag, allowing a package to declare the intents it intends to
use to query for other apps on device.

Bug: 136675067
Test: build and boot
Change-Id: Ic8b40cabddee4b6404c220901efeaae680661c0c
This commit is contained in:
Patrick Baumann
2019-06-24 15:01:49 -07:00
parent 1b23258c87
commit a4ffb4567e
7 changed files with 110 additions and 1 deletions

View File

@@ -66,6 +66,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.FileUtils;
@@ -2445,6 +2446,8 @@ public class PackageParser {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
} else if (tagName.equals("queries")) {
parseQueries(pkg, res, parser, flags, outError);
} else {
Slog.w(TAG, "Unknown element under <manifest>: " + parser.getName()
+ " at " + mArchiveSourcePath + " "
@@ -3538,6 +3541,9 @@ public class PackageParser {
owner.mRequiredAccountType = requiredAccountType;
}
owner.mForceQueryable =
sa.getBoolean(R.styleable.AndroidManifestApplication_forceQueryable, false);
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
false)) {
@@ -3953,7 +3959,6 @@ public class PackageParser {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL;
}
XmlUtils.skipCurrentTag(parser);
} else {
if (!RIGID_PARSER) {
Slog.w(TAG, "Unknown element under <application>: " + tagName
@@ -4000,6 +4005,67 @@ public class PackageParser {
return true;
}
private boolean parseQueries(Package owner, Resources res, XmlResourceParser parser, int flags,
String[] outError)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
if (parser.getName().equals("intent")) {
QueriesIntentInfo intentInfo = new QueriesIntentInfo();
if (!parseIntent(res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/,
intentInfo, outError)) {
return false;
}
Intent intent = new Intent();
if (intentInfo.countActions() != 1) {
outError[0] = "intent tags must contain exactly one action.";
return false;
}
intent.setAction(intentInfo.getAction(0));
for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
intent.addCategory(intentInfo.getCategory(i));
}
Uri data = null;
String dataType = null;
if (intentInfo.countDataTypes() > 1) {
outError[0] = "intent tag may have at most one data type.";
return false;
}
if (intentInfo.countDataSchemes() > 1) {
outError[0] = "intent tag may have at most one data scheme.";
return false;
}
if (intentInfo.countDataTypes() == 1) {
data = Uri.fromParts(intentInfo.getDataType(0), "", null);
}
if (intentInfo.countDataSchemes() == 1) {
dataType = intentInfo.getDataScheme(0);
}
intent.setDataAndType(data, dataType);
owner.mQueriesIntents = ArrayUtils.add(owner.mQueriesIntents, intent);
} else if (parser.getName().equals("package")) {
final TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestQueriesPackage);
final String packageName =
sa.getString(R.styleable.AndroidManifestQueriesPackage_name);
if (TextUtils.isEmpty(packageName)) {
outError[0] = "Package name is missing from package tag.";
return false;
}
owner.mQueriesPackages =
ArrayUtils.add(owner.mQueriesPackages, packageName.intern());
}
}
return true;
}
/**
* Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
*/
@@ -6514,6 +6580,9 @@ public class PackageParser {
// The major version code declared for this package.
public int mVersionCodeMajor;
// Whether the package declares that it should be queryable by all normal apps on device.
public boolean mForceQueryable;
// Return long containing mVersionCode and mVersionCodeMajor.
public long getLongVersionCode() {
return PackageInfo.composeLongVersionCode(mVersionCodeMajor, mVersionCode);
@@ -6619,6 +6688,9 @@ public class PackageParser {
/** Whether or not the package is a stub and must be replaced by the full version. */
public boolean isStub;
public ArrayList<String> mQueriesPackages;
public ArrayList<Intent> mQueriesIntents;
@UnsupportedAppUsage
public Package(String packageName) {
this.packageName = packageName;
@@ -7122,6 +7194,9 @@ public class PackageParser {
use32bitAbi = (dest.readInt() == 1);
restrictUpdateHash = dest.createByteArray();
visibleToInstantApps = dest.readInt() == 1;
mForceQueryable = dest.readBoolean();
mQueriesIntents = dest.createTypedArrayList(Intent.CREATOR);
mQueriesPackages = dest.createStringArrayList();
}
private static void internStringArrayList(List<String> list) {
@@ -7247,6 +7322,9 @@ public class PackageParser {
dest.writeInt(use32bitAbi ? 1 : 0);
dest.writeByteArray(restrictUpdateHash);
dest.writeInt(visibleToInstantApps ? 1 : 0);
dest.writeBoolean(mForceQueryable);
dest.writeTypedList(mQueriesIntents);
dest.writeList(mQueriesPackages);
}
@@ -8257,6 +8335,8 @@ public class PackageParser {
}
}
public static final class QueriesIntentInfo extends IntentInfo {}
public final static class ActivityIntentInfo extends IntentInfo {
@UnsupportedAppUsage
public Activity activity;

View File

@@ -4598,6 +4598,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.DeviceDefault.Light.DarkActionBar"
android:defaultToDeviceProtectedStorage="true"
android:forceQueryable="true"
android:directBootAware="true">
<activity android:name="com.android.internal.app.ChooserActivity"
android:theme="@style/Theme.DeviceDefault.Resolver"

View File

@@ -1743,6 +1743,13 @@
- {@code true} for apps with targetSdkVersion < 29.
-->
<attr name="requestLegacyExternalStorage" format="boolean" />
<!-- If {@code true} this app declares that it should be visible to all other apps on
device, regardless of what they declare via the {@code queries} tags in their
manifest.
The default value is {@code false}. -->
<attr name="forceQueryable" format="boolean" />
</declare-styleable>
<!-- The <code>permission</code> tag declares a security permission that can be
used to control access from other packages to specific components or
@@ -1977,6 +1984,12 @@
<attr name="name" />
</declare-styleable>
<declare-styleable name="AndroidManifestQueries" parent="AndroidManifest" />
<declare-styleable name="AndroidManifestQueriesPackage" parent="AndroidManifestQueries">
<attr name="name" />
</declare-styleable>
<declare-styleable name="AndroidManifestQueriesIntent" parent="AndroidManifestQueries" />
<!-- The <code>static-library</code> tag declares that this apk is providing itself
as a static shared library for other applications to use. Any app can declare such
@@ -2477,6 +2490,7 @@
<!-- High dynamic range color mode. -->
<enum name="hdr" value="2" />
</attr>
<attr name="forceQueryable" format="boolean" />
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new

View File

@@ -1700,6 +1700,12 @@
<!-- Add packages here -->
</string-array>
<string-array name="config_forceQueryablePackages" translatable="false">
<item>com.android.settings</item>
<!-- Add packages here -->
</string-array>
<!-- Component name of the default wallpaper. This will be ImageWallpaper if not
specified -->
<string name="default_wallpaper_component" translatable="false">@null</string>

View File

@@ -786,6 +786,7 @@
<java-symbol type="string" name="widget_default_class_name" />
<java-symbol type="string" name="emergency_calls_only" />
<java-symbol type="array" name="config_ephemeralResolverPackage" />
<java-symbol type="array" name="config_forceQueryablePackages" />
<java-symbol type="string" name="eventTypeAnniversary" />
<java-symbol type="string" name="eventTypeBirthday" />
<java-symbol type="string" name="eventTypeCustom" />

View File

@@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
@@ -557,6 +558,9 @@ public class PackageParserTest {
pkg.mRequiredForAllUsers = true;
pkg.visibleToInstantApps = true;
pkg.use32bitAbi = true;
pkg.mForceQueryable = true;
pkg.mQueriesPackages = new ArrayList<>(Arrays.asList("foo27"));
pkg.mQueriesIntents = new ArrayList<>(Arrays.asList(new Intent("foo28")));
}
private static void assertAllFieldsExist(PackageParser.Package pkg) throws Exception {

View File

@@ -386,6 +386,9 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
manifest_action["package-verifier"];
manifest_action["meta-data"] = meta_data_action;
manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);
manifest_action["queries"]["package"].Action(RequiredNameIsJavaPackage);
manifest_action["queries"]["intent"] = intent_filter_action;
// TODO: more complicated component name tag
manifest_action["key-sets"]["key-set"]["public-key"];
manifest_action["key-sets"]["upgrade-key-set"];