Loading tools/aapt2/link/ManifestFixer.cpp +86 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,91 @@ using android::StringPiece; namespace aapt { // This is to detect whether an <intent-filter> contains deeplink. // See https://developer.android.com/training/app-links/deep-linking. static bool HasDeepLink(xml::Element* intent_filter_el) { xml::Element* action_el = intent_filter_el->FindChild({}, "action"); xml::Element* category_el = intent_filter_el->FindChild({}, "category"); xml::Element* data_el = intent_filter_el->FindChild({}, "data"); if (action_el == nullptr || category_el == nullptr || data_el == nullptr) { return false; } // Deeplinks must specify the ACTION_VIEW intent action. constexpr const char* action_view = "android.intent.action.VIEW"; if (intent_filter_el->FindChildWithAttribute({}, "action", xml::kSchemaAndroid, "name", action_view) == nullptr) { return false; } // Deeplinks must have scheme included in <data> tag. xml::Attribute* data_scheme_attr = data_el->FindAttribute(xml::kSchemaAndroid, "scheme"); if (data_scheme_attr == nullptr || data_scheme_attr->value.empty()) { return false; } // Deeplinks must include BROWSABLE category. constexpr const char* category_browsable = "android.intent.category.BROWSABLE"; if (intent_filter_el->FindChildWithAttribute({}, "category", xml::kSchemaAndroid, "name", category_browsable) == nullptr) { return false; } return true; } static bool VerifyDeeplinkPathAttribute(xml::Element* data_el, android::SourcePathDiagnostics* diag, const std::string& attr_name) { xml::Attribute* attr = data_el->FindAttribute(xml::kSchemaAndroid, attr_name); if (attr != nullptr && !attr->value.empty()) { StringPiece attr_value = attr->value; const char* startChar = attr_value.begin(); if (attr_name == "pathPattern") { if (*startChar == '/' || *startChar == '.' || *startChar == '*') { return true; } else { diag->Error(android::DiagMessage(data_el->line_number) << "attribute 'android:" << attr_name << "' in <" << data_el->name << "> tag has value of '" << attr_value << "', it must be in a pattern start with '.' or '*', otherwise must start " "with a leading slash '/'"); return false; } } else { if (*startChar == '/') { return true; } else { diag->Error(android::DiagMessage(data_el->line_number) << "attribute 'android:" << attr_name << "' in <" << data_el->name << "> tag has value of '" << attr_value << "', it must start with a leading slash '/'"); return false; } } } return true; } static bool VerifyDeepLinkIntentAction(xml::Element* intent_filter_el, android::SourcePathDiagnostics* diag) { if (!HasDeepLink(intent_filter_el)) { return true; } xml::Element* data_el = intent_filter_el->FindChild({}, "data"); if (data_el != nullptr) { if (!VerifyDeeplinkPathAttribute(data_el, diag, "path")) { return false; } if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPrefix")) { return false; } if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPattern")) { return false; } } return true; } static bool RequiredNameIsNotEmpty(xml::Element* el, android::SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (attr == nullptr) { Loading Loading @@ -323,6 +408,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn // Common <intent-filter> actions. xml::XmlNodeAction intent_filter_action; intent_filter_action.Action(VerifyDeepLinkIntentAction); intent_filter_action["action"].Action(RequiredNameIsNotEmpty); intent_filter_action["category"].Action(RequiredNameIsNotEmpty); intent_filter_action["data"]; Loading tools/aapt2/link/ManifestFixer_test.cpp +341 −0 Original line number Diff line number Diff line Loading @@ -1068,4 +1068,345 @@ TEST_F(ManifestFixerTest, ComponentPropertyOnlyOneAttributeDefined) { </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } TEST_F(ManifestFixerTest, IntentFilterActionMustHaveNonEmptyName) { std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } TEST_F(ManifestFixerTest, IntentFilterCategoryMustHaveNonEmptyName) { std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <category android:name="" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <category /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } TEST_F(ManifestFixerTest, IntentFilterPathMustStartWithLeadingSlashOnDeepLinks) { // No DeepLink. std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <data /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // No DeepLink, missing ACTION_VIEW. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink, missing DEFAULT category while DEFAULT is recommended but not required. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); // No DeepLink, missing BROWSABLE category. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // No DeepLink, missing 'android:scheme' in <data> tag. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // No DeepLink, <action> is ACTION_MAIN not ACTION_VIEW. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with no leading slash in android:path. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:path="path" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); // DeepLink with leading slash in android:path. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:path="/path" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with no leading slash in android:pathPrefix. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPrefix" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); // DeepLink with leading slash in android:pathPrefix. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="/pathPrefix" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with no leading slash in android:pathPattern. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPattern="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); // DeepLink with leading slash in android:pathPattern. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPattern="/pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with '.' start in pathPattern. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPattern=".*\\.pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with '*' start in pathPattern. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPattern="*" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } } // namespace aapt Loading
tools/aapt2/link/ManifestFixer.cpp +86 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,91 @@ using android::StringPiece; namespace aapt { // This is to detect whether an <intent-filter> contains deeplink. // See https://developer.android.com/training/app-links/deep-linking. static bool HasDeepLink(xml::Element* intent_filter_el) { xml::Element* action_el = intent_filter_el->FindChild({}, "action"); xml::Element* category_el = intent_filter_el->FindChild({}, "category"); xml::Element* data_el = intent_filter_el->FindChild({}, "data"); if (action_el == nullptr || category_el == nullptr || data_el == nullptr) { return false; } // Deeplinks must specify the ACTION_VIEW intent action. constexpr const char* action_view = "android.intent.action.VIEW"; if (intent_filter_el->FindChildWithAttribute({}, "action", xml::kSchemaAndroid, "name", action_view) == nullptr) { return false; } // Deeplinks must have scheme included in <data> tag. xml::Attribute* data_scheme_attr = data_el->FindAttribute(xml::kSchemaAndroid, "scheme"); if (data_scheme_attr == nullptr || data_scheme_attr->value.empty()) { return false; } // Deeplinks must include BROWSABLE category. constexpr const char* category_browsable = "android.intent.category.BROWSABLE"; if (intent_filter_el->FindChildWithAttribute({}, "category", xml::kSchemaAndroid, "name", category_browsable) == nullptr) { return false; } return true; } static bool VerifyDeeplinkPathAttribute(xml::Element* data_el, android::SourcePathDiagnostics* diag, const std::string& attr_name) { xml::Attribute* attr = data_el->FindAttribute(xml::kSchemaAndroid, attr_name); if (attr != nullptr && !attr->value.empty()) { StringPiece attr_value = attr->value; const char* startChar = attr_value.begin(); if (attr_name == "pathPattern") { if (*startChar == '/' || *startChar == '.' || *startChar == '*') { return true; } else { diag->Error(android::DiagMessage(data_el->line_number) << "attribute 'android:" << attr_name << "' in <" << data_el->name << "> tag has value of '" << attr_value << "', it must be in a pattern start with '.' or '*', otherwise must start " "with a leading slash '/'"); return false; } } else { if (*startChar == '/') { return true; } else { diag->Error(android::DiagMessage(data_el->line_number) << "attribute 'android:" << attr_name << "' in <" << data_el->name << "> tag has value of '" << attr_value << "', it must start with a leading slash '/'"); return false; } } } return true; } static bool VerifyDeepLinkIntentAction(xml::Element* intent_filter_el, android::SourcePathDiagnostics* diag) { if (!HasDeepLink(intent_filter_el)) { return true; } xml::Element* data_el = intent_filter_el->FindChild({}, "data"); if (data_el != nullptr) { if (!VerifyDeeplinkPathAttribute(data_el, diag, "path")) { return false; } if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPrefix")) { return false; } if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPattern")) { return false; } } return true; } static bool RequiredNameIsNotEmpty(xml::Element* el, android::SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (attr == nullptr) { Loading Loading @@ -323,6 +408,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn // Common <intent-filter> actions. xml::XmlNodeAction intent_filter_action; intent_filter_action.Action(VerifyDeepLinkIntentAction); intent_filter_action["action"].Action(RequiredNameIsNotEmpty); intent_filter_action["category"].Action(RequiredNameIsNotEmpty); intent_filter_action["data"]; Loading
tools/aapt2/link/ManifestFixer_test.cpp +341 −0 Original line number Diff line number Diff line Loading @@ -1068,4 +1068,345 @@ TEST_F(ManifestFixerTest, ComponentPropertyOnlyOneAttributeDefined) { </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } TEST_F(ManifestFixerTest, IntentFilterActionMustHaveNonEmptyName) { std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } TEST_F(ManifestFixerTest, IntentFilterCategoryMustHaveNonEmptyName) { std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <category android:name="" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <category /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } TEST_F(ManifestFixerTest, IntentFilterPathMustStartWithLeadingSlashOnDeepLinks) { // No DeepLink. std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <data /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // No DeepLink, missing ACTION_VIEW. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink, missing DEFAULT category while DEFAULT is recommended but not required. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); // No DeepLink, missing BROWSABLE category. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // No DeepLink, missing 'android:scheme' in <data> tag. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // No DeepLink, <action> is ACTION_MAIN not ACTION_VIEW. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with no leading slash in android:path. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:path="path" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); // DeepLink with leading slash in android:path. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:path="/path" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with no leading slash in android:pathPrefix. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="pathPrefix" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); // DeepLink with leading slash in android:pathPrefix. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPrefix="/pathPrefix" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with no leading slash in android:pathPattern. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPattern="pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), IsNull()); // DeepLink with leading slash in android:pathPattern. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPattern="/pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with '.' start in pathPattern. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPattern=".*\\.pathPattern" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); // DeepLink with '*' start in pathPattern. input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="www.example.com" android:pathPattern="*" /> </intent-filter> </activity> </application> </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } } // namespace aapt