blob: 31fedb1a737db853607d1f77e9bc1231c93ac794 [file] [log] [blame]
/*
* Copyright (C) 2011 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.
*/
package com.android.tools.lint.checks;
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.model.AndroidProject;
import com.android.builder.model.ApiVersion;
import com.android.builder.model.BuildType;
import com.android.builder.model.BuildTypeContainer;
import com.android.builder.model.ProductFlavor;
import com.android.builder.model.ProductFlavorContainer;
import com.android.builder.model.SourceProvider;
import com.android.builder.model.SourceProviderContainer;
import com.android.builder.model.Variant;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Project;
import com.google.common.collect.Lists;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@SuppressWarnings("javadoc")
public class ManifestDetectorTest extends AbstractCheckTest {
@Override
protected Detector getDetector() {
return new ManifestDetector();
}
private Set<Issue> mEnabled = new HashSet<Issue>();
@Override
protected TestConfiguration getConfiguration(LintClient client, Project project) {
return new TestConfiguration(client, project, null) {
@Override
public boolean isEnabled(@NonNull Issue issue) {
return super.isEnabled(issue) && mEnabled.contains(issue);
}
};
}
public void testOrderOk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ORDER);
assertEquals(
"No warnings.",
lintProject(
"AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testBrokenOrder() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ORDER);
assertEquals(
"AndroidManifest.xml:16: Warning: <uses-sdk> tag appears after <application> tag [ManifestOrder]\n" +
" <uses-sdk android:minSdkVersion=\"Froyo\" />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings\n" +
"",
lintProject(
"broken-manifest.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testMissingUsesSdk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
assertEquals(
"AndroidManifest.xml: Warning: Manifest should specify a minimum API level with <uses-sdk android:minSdkVersion=\"?\" />; if it really supports all versions of Android set it to 1. [UsesMinSdkAttributes]\n" +
"0 errors, 1 warnings\n",
lintProject(
"missingusessdk.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testMissingUsesSdkInGradle() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
assertEquals(""
+ "No warnings.",
lintProject("missingusessdk.xml=>AndroidManifest.xml",
"multiproject/library.properties=>build.gradle")); // dummy; only name counts
}
public void testMissingMinSdk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
assertEquals(
"AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a minimum API level with android:minSdkVersion=\"?\" [UsesMinSdkAttributes]\n" +
" <uses-sdk android:targetSdkVersion=\"10\" />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings\n" +
"",
lintProject(
"missingmin.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testMissingTargetSdk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
assertEquals(
"AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a target API level (the highest verified version; when running on later versions, compatibility behaviors may be enabled) with android:targetSdkVersion=\"?\" [UsesMinSdkAttributes]\n" +
" <uses-sdk android:minSdkVersion=\"10\" />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings\n",
lintProject(
"missingtarget.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testOldTargetSdk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.TARGET_NEWER);
assertEquals(
"AndroidManifest.xml:7: Warning: Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details. [OldTargetApi]\n" +
" <uses-sdk android:minSdkVersion=\"10\" android:targetSdkVersion=\"14\" />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings\n",
lintProject(
"oldtarget.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testMultipleSdk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.MULTIPLE_USES_SDK);
assertEquals(
"AndroidManifest.xml:8: Error: There should only be a single <uses-sdk> element in the manifest: merge these together [MultipleUsesSdk]\n" +
" <uses-sdk android:targetSdkVersion=\"14\" />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
" AndroidManifest.xml:7: Also appears here\n" +
" AndroidManifest.xml:9: Also appears here\n" +
"1 errors, 0 warnings\n",
lintProject(
"multiplesdk.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testWrongLocation() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.WRONG_PARENT);
assertEquals(
"AndroidManifest.xml:8: Error: The <uses-sdk> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <uses-sdk android:minSdkVersion=\"Froyo\" />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:9: Error: The <uses-permission> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <uses-permission />\n" +
" ~~~~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:10: Error: The <permission> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <permission />\n" +
" ~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:11: Error: The <permission-tree> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <permission-tree />\n" +
" ~~~~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:12: Error: The <permission-group> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <permission-group />\n" +
" ~~~~~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:14: Error: The <uses-sdk> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <uses-sdk />\n" +
" ~~~~~~~~~~~~\n" +
"AndroidManifest.xml:15: Error: The <uses-configuration> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <uses-configuration />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:16: Error: The <uses-feature> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <uses-feature />\n" +
" ~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:17: Error: The <supports-screens> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <supports-screens />\n" +
" ~~~~~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:18: Error: The <compatible-screens> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <compatible-screens />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:19: Error: The <supports-gl-texture> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
" <supports-gl-texture />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:24: Error: The <uses-library> element must be a direct child of the <application> element [WrongManifestParent]\n" +
" <uses-library />\n" +
" ~~~~~~~~~~~~~~~~\n" +
"AndroidManifest.xml:25: Error: The <activity> element must be a direct child of the <application> element [WrongManifestParent]\n" +
" <activity android:name=\".HelloWorld\"\n" +
" ^\n" +
"13 errors, 0 warnings\n" +
"",
lintProject("broken-manifest2.xml=>AndroidManifest.xml"));
}
public void testDuplicateActivity() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
assertEquals(
"AndroidManifest.xml:16: Error: Duplicate registration for activity com.example.helloworld.HelloWorld [DuplicateActivity]\n" +
" <activity android:name=\"com.example.helloworld.HelloWorld\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings\n" +
"",
lintProject(
"duplicate-manifest.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testDuplicateActivityAcrossSourceSets() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
File master = getProjectDir("MasterProject",
// Master project
"AndroidManifest.xml=>AndroidManifest.xml",
"multiproject/main-merge.properties=>project.properties",
"multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
);
File library = getProjectDir("LibraryProject",
// Library project
"AndroidManifest.xml=>AndroidManifest.xml",
"multiproject/library.properties=>project.properties",
"multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
"multiproject/strings.xml=>res/values/strings.xml"
);
assertEquals("No warnings.",
checkLint(Arrays.asList(master, library)));
}
public void testIgnoreDuplicateActivity() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
assertEquals(
"No warnings.",
lintProject(
"duplicate-manifest-ignore.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testAllowBackup() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals(
"AndroidManifest.xml:9: Warning: Should explicitly set android:allowBackup to " +
"true or false (it's true by default, and that can have some security " +
"implications for the application's data) [AllowBackup]\n" +
" <application\n" +
" ^\n" +
"0 errors, 1 warnings\n",
lintProject(
"AndroidManifest.xml",
"apicheck/minsdk14.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testAllowBackupOk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals(
"No warnings.",
lintProject(
"allowbackup.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testAllowBackupOk2() throws Exception {
// Requires build api >= 4
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals(
"No warnings.",
lintProject(
"apicheck/minsdk1.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testAllowBackupOk3() throws Exception {
// Not flagged in library projects
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals(
"No warnings.",
lintProject(
"AndroidManifest.xml",
"multiproject/library.properties=>project.properties",
"res/values/strings.xml"));
}
public void testAllowIgnore() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals(
"No warnings.",
lintProject(
"allowbackup_ignore.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testDuplicatePermissions() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.UNIQUE_PERMISSION);
assertEquals(
"AndroidManifest.xml:12: Error: Permission name SEND_SMS is not unique (appears in both foo.permission.SEND_SMS and bar.permission.SEND_SMS) [UniquePermission]\n" +
" <permission android:name=\"bar.permission.SEND_SMS\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
" AndroidManifest.xml:9: Previous permission here\n" +
"1 errors, 0 warnings\n",
lintProject(
"duplicate_permissions1.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testDuplicatePermissionsMultiProject() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.UNIQUE_PERMISSION);
File master = getProjectDir("MasterProject",
// Master project
"duplicate_permissions2.xml=>AndroidManifest.xml",
"multiproject/main-merge.properties=>project.properties",
"multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
);
File library = getProjectDir("LibraryProject",
// Library project
"duplicate_permissions3.xml=>AndroidManifest.xml",
"multiproject/library.properties=>project.properties",
"multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
"multiproject/strings.xml=>res/values/strings.xml"
);
assertEquals(
"LibraryProject/AndroidManifest.xml:9: Error: Permission name SEND_SMS is not unique (appears in both foo.permission.SEND_SMS and bar.permission.SEND_SMS) [UniquePermission]\n" +
" <permission android:name=\"bar.permission.SEND_SMS\"\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings\n",
checkLint(Arrays.asList(master, library)));
}
public void testMissingVersion() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
assertEquals(""
+ "AndroidManifest.xml:2: Warning: Should set android:versionCode to specify the application version [MissingVersion]\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ "^\n"
+ "AndroidManifest.xml:2: Warning: Should set android:versionName to specify the application version [MissingVersion]\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ "^\n"
+ "0 errors, 2 warnings\n",
lintProject("no_version.xml=>AndroidManifest.xml"));
}
public void testVersionNotMissingInGradleProjects() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
assertEquals(""
+ "No warnings.",
lintProject("no_version.xml=>AndroidManifest.xml",
"multiproject/library.properties=>build.gradle")); // dummy; only name counts
}
public void testIllegalReference() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ILLEGAL_REFERENCE);
assertEquals(""
+ "AndroidManifest.xml:4: Warning: The android:versionCode cannot be a resource url, it must be a literal integer [IllegalResourceRef]\n"
+ " android:versionCode=\"@dimen/versionCode\"\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:7: Warning: The android:minSdkVersion cannot be a resource url, it must be a literal integer (or string if a preview codename) [IllegalResourceRef]\n"
+ " <uses-sdk android:minSdkVersion=\"@dimen/minSdkVersion\" android:targetSdkVersion=\"@dimen/targetSdkVersion\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:7: Warning: The android:targetSdkVersion cannot be a resource url, it must be a literal integer (or string if a preview codename) [IllegalResourceRef]\n"
+ " <uses-sdk android:minSdkVersion=\"@dimen/minSdkVersion\" android:targetSdkVersion=\"@dimen/targetSdkVersion\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 3 warnings\n",
lintProject("illegal_version.xml=>AndroidManifest.xml"));
}
public void testDuplicateUsesFeature() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_USES_FEATURE);
assertEquals(
"AndroidManifest.xml:11: Warning: Duplicate declaration of uses-feature android.hardware.camera [DuplicateUsesFeature]\n" +
" <uses-feature android:name=\"android.hardware.camera\"/>\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings\n",
lintProject(
"duplicate_uses_feature.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testDuplicateUsesFeatureOk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_USES_FEATURE);
assertEquals(
"No warnings.",
lintProject(
"duplicate_uses_feature_ok.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testMissingApplicationIcon() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
assertEquals(
"AndroidManifest.xml:9: Warning: Should explicitly set android:icon, there is no default [MissingApplicationIcon]\n" +
" <application\n" +
" ^\n" +
"0 errors, 1 warnings\n",
lintProject(
"missing_application_icon.xml=>AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testMissingApplicationIconInLibrary() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
assertEquals(
"No warnings.",
lintProject(
"missing_application_icon.xml=>AndroidManifest.xml",
"multiproject/library.properties=>project.properties",
"res/values/strings.xml"));
}
public void testMissingApplicationIconOk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
assertEquals(
"No warnings.",
lintProject(
"AndroidManifest.xml",
"res/values/strings.xml"));
}
public void testDeviceAdmin() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.DEVICE_ADMIN);
assertEquals(""
+ "AndroidManifest.xml:31: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
+ " <meta-data android:name=\"android.app.device_admin\"\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:44: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
+ " <meta-data android:name=\"android.app.device_admin\"\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:56: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
+ " <meta-data android:name=\"android.app.device_admin\"\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 3 warnings\n",
lintProject("deviceadmin.xml=>AndroidManifest.xml"));
}
public void testMockLocations() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.MOCK_LOCATION);
assertEquals(""
+ "AndroidManifest.xml:9: Error: Mock locations should only be requested in a test or debug-specific manifest file (typically src/debug/AndroidManifest.xml) [MockLocation]\n"
+ " <uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\" /> \n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
"mock_location.xml=>AndroidManifest.xml",
"mock_location.xml=>debug/AndroidManifest.xml",
"mock_location.xml=>test/AndroidManifest.xml",
"multiproject/library.properties=>build.gradle")); // dummy; only name counts
// TODO: When we have an instantiatable gradle model, test with real model and verify
// that a manifest file in a debug build type does not get flagged.
}
public void testMockLocationsOk() throws Exception {
// Not a Gradle project
mEnabled = Collections.singleton(ManifestDetector.MOCK_LOCATION);
assertEquals(""
+ "No warnings.",
lintProject(
"mock_location.xml=>AndroidManifest.xml"));
}
public void testGradleOverrides() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
assertEquals(""
+ "AndroidManifest.xml:4: Warning: This versionCode value (1) is not used; it is always overridden by the value specified in the Gradle build script (2) [GradleOverrides]\n"
+ " android:versionCode=\"1\"\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:5: Warning: This versionName value (1.0) is not used; it is always overridden by the value specified in the Gradle build script (MyName) [GradleOverrides]\n"
+ " android:versionName=\"1.0\" >\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:7: Warning: This minSdkVersion value (14) is not used; it is always overridden by the value specified in the Gradle build script (5) [GradleOverrides]\n"
+ " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"17\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:7: Warning: This targetSdkVersion value (17) is not used; it is always overridden by the value specified in the Gradle build script (16) [GradleOverrides]\n"
+ " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"17\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 4 warnings\n",
lintProject(
"gradle_override.xml=>AndroidManifest.xml",
"multiproject/library.properties=>build.gradle")); // dummy; only name counts
}
public void testGradleOverridesOk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
// (See custom logic in #createClient which returns -1/null for the merged flavor
// from this test, and not from testGradleOverrides)
assertEquals(""
+ "No warnings.",
lintProject(
"gradle_override.xml=>AndroidManifest.xml",
"multiproject/library.properties=>build.gradle")); // dummy; only name counts
}
public void testManifestPackagePlaceholder() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
assertEquals(""
+ "AndroidManifest.xml:3: Warning: Cannot use placeholder for the package in the manifest; set applicationId in build.gradle instead [GradleOverrides]\n"
+ " package=\"${packageName}\" >\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 1 warnings\n",
lintProject(
"gradle_override_placeholder.xml=>AndroidManifest.xml",
"multiproject/library.properties=>build.gradle")); // dummy; only name counts
}
public void testMipMap() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.MIPMAP);
assertEquals("No warnings.",
lintProject(
"mipmap.xml=>AndroidManifest.xml"));
}
public void testMipMapWithDensityFiltering() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.MIPMAP);
assertEquals(""
+ "AndroidManifest.xml:9: Warning: Should use @mipmap instead of @drawable for launcher icons [MipmapIcons]\n"
+ " android:icon=\"@drawable/ic_launcher\"\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "AndroidManifest.xml:14: Warning: Should use @mipmap instead of @drawable for launcher icons [MipmapIcons]\n"
+ " android:icon=\"@drawable/activity1\"\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 2 warnings\n",
lintProject(
"mipmap.xml=>AndroidManifest.xml"));
}
public void testFullBackupContentBoolean() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals("No warnings.",
lintProjectIncrementally(
"AndroidManifest.xml",
xml("AndroidManifest.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"com.example.helloworld\" >\n"
+ "\n"
+ " <application\n"
+ " android:allowBackup=\"true\"\n"
+ " android:fullBackupContent=\"true\"\n"
+ " android:label=\"@string/app_name\"\n"
+ " android:theme=\"@style/AppTheme\" >\n"
+ " </application>\n"
+ "\n"
+ "</manifest>\n")));
}
public void testFullBackupContentMissing() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals(""
+ "AndroidManifest.xml:7: Warning: Missing <full-backup-content> resource [AllowBackup]\n"
+ " android:fullBackupContent=\"@xml/backup\"\n"
+ " ~~~~~~~~~~~\n"
+ "0 errors, 1 warnings\n",
lintProjectIncrementally(
"AndroidManifest.xml",
xml("AndroidManifest.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"com.example.helloworld\" >\n"
+ "\n"
+ " <application\n"
+ " android:allowBackup=\"true\"\n"
+ " android:fullBackupContent=\"@xml/backup\"\n"
+ " android:label=\"@string/app_name\"\n"
+ " android:theme=\"@style/AppTheme\" >\n"
+ " </application>\n"
+ "\n"
+ "</manifest>\n")));
}
public void testFullBackupContentOk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals("No warnings.",
lintProjectIncrementally(
"AndroidManifest.xml",
xml("AndroidManifest.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"com.example.helloworld\" >\n"
+ "\n"
+ " <application\n"
+ " android:allowBackup=\"true\"\n"
+ " android:fullBackupContent=\"@xml/backup\"\n"
+ " android:label=\"@string/app_name\"\n"
+ " android:theme=\"@style/AppTheme\" >\n"
+ " </application>\n"
+ "\n"
+ "</manifest>\n"),
xml("res/xml/backup.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<full-backup-content>\n"
+ " <include domain=\"file\" path=\"dd\"/>\n"
+ " <exclude domain=\"file\" path=\"dd/fo3o.txt\"/>\n"
+ " <exclude domain=\"file\" path=\"dd/ss/foo.txt\"/>\n"
+ "</full-backup-content>")));
}
public void testHasBackupSpecifiedInTarget23() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals("No warnings.",
lintProject(
xml("AndroidManifest.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"com.example.helloworld\" >\n"
+ " <uses-sdk android:targetSdkVersion=\"23\" />"
+ "\n"
+ " <application\n"
+ " android:fullBackupContent=\"no\"\n"
+ " android:label=\"@string/app_name\"\n"
+ " android:theme=\"@style/AppTheme\" >\n"
+ " </application>\n"
+ "\n"
+ "</manifest>\n")));
}
public void testMissingBackupInTarget23() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals(""
+ "AndroidManifest.xml:5: Warning: Should explicitly set android:fullBackupContent to true or false to opt-in to or out of full app data back-up and restore, or alternatively to an @xml resource which specifies which files to backup [AllowBackup]\n"
+ " <application\n"
+ " ^\n"
+ "0 errors, 1 warnings\n",
lintProject(
xml("AndroidManifest.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"com.example.helloworld\" >\n"
+ " <uses-sdk android:targetSdkVersion=\"23\" />"
+ "\n"
+ " <application\n"
+ " android:label=\"@string/app_name\"\n"
+ " android:theme=\"@style/AppTheme\" >\n"
+ " </application>\n"
+ "\n"
+ "</manifest>\n")));
}
public void testMissingBackupWithoutGcmPreTarget23() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals("No warnings.",
lintProject(
xml("AndroidManifest.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"com.example.helloworld\" >\n"
+ " <uses-sdk android:targetSdkVersion=\"21\" />"
+ "\n"
+ " <application\n"
+ " android:label=\"@string/app_name\"\n"
+ " android:theme=\"@style/AppTheme\" >\n"
+ " </application>\n"
+ "\n"
+ "</manifest>\n")));
}
public void testMissingBackupWithGcmPreTarget23() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
assertEquals(""
+ "AndroidManifest.xml:5: Warning: Should explicitly set android:fullBackupContent to avoid backing up the GCM device specific regId. [AllowBackup]\n"
+ " <application\n"
+ " ^\n"
+ "0 errors, 1 warnings\n",
lintProject(
xml("AndroidManifest.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"com.example.helloworld\" >\n"
+ " <uses-sdk android:targetSdkVersion=\"21\" />"
+ "\n"
+ " <application\n"
+ " android:label=\"@string/app_name\"\n"
+ " android:theme=\"@style/AppTheme\" >"
+ " <receiver\n"
+ " android:name=\".GcmBroadcastReceiver\"\n"
+ " android:permission=\"com.google.android.c2dm.permission.SEND\" >\n"
+ " <intent-filter>\n"
+ " <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n"
+ " <category android:name=\"com.example.gcm\" />\n"
+ " </intent-filter>\n"
+ " </receiver>\n"
+ " </application>\n"
+ "\n"
+ "</manifest>\n")));
}
// Custom project which locates all manifest files in the project rather than just
// being hardcoded to the root level
@Override
protected TestLintClient createClient() {
if ("testMipMapWithDensityFiltering".equals(getName())) {
// Set up a mock project model for the resource configuration test(s)
// where we provide a subset of densities to be included
return new TestLintClient() {
@NonNull
@Override
protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
return new Project(this, dir, referenceDir) {
@Override
public boolean isGradleProject() {
return true;
}
@Nullable
@Override
public AndroidProject getGradleProjectModel() {
/*
Simulate variant freeBetaDebug in this setup:
defaultConfig {
...
resConfigs "cs"
}
flavorDimensions "pricing", "releaseType"
productFlavors {
beta {
flavorDimension "releaseType"
resConfig "en", "de"
resConfigs "nodpi", "hdpi"
}
normal { flavorDimension "releaseType" }
free { flavorDimension "pricing" }
paid { flavorDimension "pricing" }
}
*/
ProductFlavor flavorFree = mock(ProductFlavor.class);
when(flavorFree.getName()).thenReturn("free");
when(flavorFree.getResourceConfigurations())
.thenReturn(Collections.<String>emptyList());
ProductFlavor flavorNormal = mock(ProductFlavor.class);
when(flavorNormal.getName()).thenReturn("normal");
when(flavorNormal.getResourceConfigurations())
.thenReturn(Collections.<String>emptyList());
ProductFlavor flavorPaid = mock(ProductFlavor.class);
when(flavorPaid.getName()).thenReturn("paid");
when(flavorPaid.getResourceConfigurations())
.thenReturn(Collections.<String>emptyList());
ProductFlavor flavorBeta = mock(ProductFlavor.class);
when(flavorBeta.getName()).thenReturn("beta");
List<String> resConfigs = Arrays.asList("hdpi", "en", "de", "nodpi");
when(flavorBeta.getResourceConfigurations()).thenReturn(resConfigs);
ProductFlavor defaultFlavor = mock(ProductFlavor.class);
when(defaultFlavor.getName()).thenReturn("main");
when(defaultFlavor.getResourceConfigurations()).thenReturn(
Collections.singleton("cs"));
ProductFlavorContainer containerBeta =
mock(ProductFlavorContainer.class);
when(containerBeta.getProductFlavor()).thenReturn(flavorBeta);
ProductFlavorContainer containerFree =
mock(ProductFlavorContainer.class);
when(containerFree.getProductFlavor()).thenReturn(flavorFree);
ProductFlavorContainer containerPaid =
mock(ProductFlavorContainer.class);
when(containerPaid.getProductFlavor()).thenReturn(flavorPaid);
ProductFlavorContainer containerNormal =
mock(ProductFlavorContainer.class);
when(containerNormal.getProductFlavor()).thenReturn(flavorNormal);
ProductFlavorContainer defaultContainer =
mock(ProductFlavorContainer.class);
when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
List<ProductFlavorContainer> containers = Arrays.asList(
containerPaid, containerFree, containerNormal, containerBeta
);
AndroidProject project = mock(AndroidProject.class);
when(project.getProductFlavors()).thenReturn(containers);
when(project.getDefaultConfig()).thenReturn(defaultContainer);
return project;
}
@Nullable
@Override
public Variant getCurrentVariant() {
List<String> productFlavorNames = Arrays.asList("free", "beta");
Variant mock = mock(Variant.class);
when(mock.getProductFlavors()).thenReturn(productFlavorNames);
return mock;
}
};
}
};
}
if (mEnabled.contains(ManifestDetector.MOCK_LOCATION)) {
return new TestLintClient() {
@NonNull
@Override
protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
return new Project(this, dir, referenceDir) {
@NonNull
@Override
public List<File> getManifestFiles() {
if (mManifestFiles == null) {
mManifestFiles = Lists.newArrayList();
addManifestFiles(mDir);
}
return mManifestFiles;
}
private void addManifestFiles(File dir) {
if (dir.getName().equals(ANDROID_MANIFEST_XML)) {
mManifestFiles.add(dir);
} else if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
addManifestFiles(file);
}
}
}
}
@NonNull SourceProvider createSourceProvider(File manifest) {
SourceProvider provider = mock(SourceProvider.class);
when(provider.getManifestFile()).thenReturn(manifest);
return provider;
}
@Nullable
@Override
public AndroidProject getGradleProjectModel() {
if (!isGradleProject()) {
return null;
}
File main = new File(mDir, ANDROID_MANIFEST_XML);
File debug = new File(mDir, "debug" + File.separator + ANDROID_MANIFEST_XML);
File test = new File(mDir, "test" + File.separator + ANDROID_MANIFEST_XML);
SourceProvider defaultSourceProvider = createSourceProvider(main);
SourceProvider debugSourceProvider = createSourceProvider(debug);
SourceProvider testSourceProvider = createSourceProvider(test);
ProductFlavorContainer defaultConfig = mock(ProductFlavorContainer.class);
when(defaultConfig.getSourceProvider()).thenReturn(defaultSourceProvider);
BuildType buildType = mock(BuildType.class);
when(buildType.isDebuggable()).thenReturn(true);
BuildTypeContainer buildTypeContainer = mock(BuildTypeContainer.class);
when(buildTypeContainer.getBuildType()).thenReturn(buildType);
when(buildTypeContainer.getSourceProvider()).thenReturn(debugSourceProvider);
List<BuildTypeContainer> buildTypes = Lists.newArrayList(buildTypeContainer);
SourceProviderContainer extraProvider = mock(SourceProviderContainer.class);
when(extraProvider.getArtifactName()).thenReturn(AndroidProject.ARTIFACT_ANDROID_TEST);
when(extraProvider.getSourceProvider()).thenReturn(testSourceProvider);
List<SourceProviderContainer> extraProviders = Lists.newArrayList(extraProvider);
ProductFlavorContainer productFlavorContainer = mock(ProductFlavorContainer.class);
when(productFlavorContainer.getExtraSourceProviders()).thenReturn(extraProviders);
List<ProductFlavorContainer> productFlavors = Lists.newArrayList(productFlavorContainer);
AndroidProject project = mock(AndroidProject.class);
when(project.getDefaultConfig()).thenReturn(defaultConfig);
when(project.getBuildTypes()).thenReturn(buildTypes);
when(project.getProductFlavors()).thenReturn(productFlavors);
return project;
}
};
}
};
} else if (mEnabled.contains(ManifestDetector.GRADLE_OVERRIDES)) {
return new TestLintClient() {
@NonNull
@Override
protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
return new Project(this, dir, referenceDir) {
@Override
public boolean isGradleProject() {
return true;
}
@Nullable
@Override
public Variant getCurrentVariant() {
ProductFlavor flavor = mock(ProductFlavor.class);
if (getName().equals("ManifestDetectorTest_testGradleOverridesOk") ||
getName().equals(
"ManifestDetectorTest_testManifestPackagePlaceholder")) {
when(flavor.getMinSdkVersion()).thenReturn(null);
when(flavor.getTargetSdkVersion()).thenReturn(null);
when(flavor.getVersionCode()).thenReturn(null);
when(flavor.getVersionName()).thenReturn(null);
} else {
assertEquals(getName(), "ManifestDetectorTest_testGradleOverrides");
ApiVersion apiMock = mock(ApiVersion.class);
when(apiMock.getApiLevel()).thenReturn(5);
when(apiMock.getApiString()).thenReturn("5");
when(flavor.getMinSdkVersion()).thenReturn(apiMock);
apiMock = mock(ApiVersion.class);
when(apiMock.getApiLevel()).thenReturn(16);
when(apiMock.getApiString()).thenReturn("16");
when(flavor.getTargetSdkVersion()).thenReturn(apiMock);
when(flavor.getVersionCode()).thenReturn(2);
when(flavor.getVersionName()).thenReturn("MyName");
}
Variant mock = mock(Variant.class);
when(mock.getMergedFlavor()).thenReturn(flavor);
return mock;
}
};
}
};
}
return super.createClient();
}
}