/*
* Copyright (C) 2015 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.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
import com.android.tools.lint.ExternalAnnotationRepository;
import com.android.tools.lint.ExternalAnnotationRepositoryTest;
import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
import com.android.tools.lint.detector.api.Detector;
@SuppressWarnings("ClassNameDiffersFromFileName") // For embedded unit tests
public class SupportAnnotationDetectorTest extends AbstractCheckTest {
private static final boolean SDK_ANNOTATIONS_AVAILABLE =
new SupportAnnotationDetectorTest().createClient().findResource(
ExternalAnnotationRepository.SDK_ANNOTATIONS_PATH) != null;
@Override
protected Detector getDetector() {
return new SupportAnnotationDetector();
}
public void testRange() throws Exception {
assertEquals(""
+ "src/test/pkg/RangeTest.java:32: Error: Expected length 5 (was 4) [Range]\n"
+ " printExact(\"1234\"); // ERROR\n"
+ " ~~~~~~\n"
+ "src/test/pkg/RangeTest.java:34: Error: Expected length 5 (was 6) [Range]\n"
+ " printExact(\"123456\"); // ERROR\n"
+ " ~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:36: Error: Expected length ≥ 5 (was 4) [Range]\n"
+ " printMin(\"1234\"); // ERROR\n"
+ " ~~~~~~\n"
+ "src/test/pkg/RangeTest.java:43: Error: Expected length ≤ 8 (was 9) [Range]\n"
+ " printMax(\"123456789\"); // ERROR\n"
+ " ~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:45: Error: Expected length ≥ 4 (was 3) [Range]\n"
+ " printRange(\"123\"); // ERROR\n"
+ " ~~~~~\n"
+ "src/test/pkg/RangeTest.java:49: Error: Expected length ≤ 6 (was 7) [Range]\n"
+ " printRange(\"1234567\"); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:53: Error: Expected size 5 (was 4) [Range]\n"
+ " printExact(new int[]{1, 2, 3, 4}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:55: Error: Expected size 5 (was 6) [Range]\n"
+ " printExact(new int[]{1, 2, 3, 4, 5, 6}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:57: Error: Expected size ≥ 5 (was 4) [Range]\n"
+ " printMin(new int[]{1, 2, 3, 4}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:65: Error: Expected size ≤ 8 (was 9) [Range]\n"
+ " printMax(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:67: Error: Expected size ≥ 4 (was 3) [Range]\n"
+ " printRange(new int[] {1,2,3}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:71: Error: Expected size ≤ 6 (was 7) [Range]\n"
+ " printRange(new int[] {1,2,3,4,5,6,7}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:74: Error: Expected size to be a multiple of 3 (was 4 and should be either 3 or 6) [Range]\n"
+ " printMultiple(new int[] {1,2,3,4}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:75: Error: Expected size to be a multiple of 3 (was 5 and should be either 3 or 6) [Range]\n"
+ " printMultiple(new int[] {1,2,3,4,5}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:77: Error: Expected size to be a multiple of 3 (was 7 and should be either 6 or 9) [Range]\n"
+ " printMultiple(new int[] {1,2,3,4,5,6,7}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:80: Error: Expected size ≥ 4 (was 3) [Range]\n"
+ " printMinMultiple(new int[]{1, 2, 3}); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RangeTest.java:84: Error: Value must be ≥ 4 (was 3) [Range]\n"
+ " printAtLeast(3); // ERROR\n"
+ " ~\n"
+ "src/test/pkg/RangeTest.java:91: Error: Value must be ≤ 7 (was 8) [Range]\n"
+ " printAtMost(8); // ERROR\n"
+ " ~\n"
+ "src/test/pkg/RangeTest.java:93: Error: Value must be ≥ 4 (was 3) [Range]\n"
+ " printBetween(3); // ERROR\n"
+ " ~\n"
+ "src/test/pkg/RangeTest.java:98: Error: Value must be ≤ 7 (was 8) [Range]\n"
+ " printBetween(8); // ERROR\n"
+ " ~\n"
+ "src/test/pkg/RangeTest.java:102: Error: Value must be ≥ 2.5 (was 2.49) [Range]\n"
+ " printAtLeastInclusive(2.49f); // ERROR\n"
+ " ~~~~~\n"
+ "src/test/pkg/RangeTest.java:106: Error: Value must be > 2.5 (was 2.49) [Range]\n"
+ " printAtLeastExclusive(2.49f); // ERROR\n"
+ " ~~~~~\n"
+ "src/test/pkg/RangeTest.java:107: Error: Value must be > 2.5 (was 2.5) [Range]\n"
+ " printAtLeastExclusive(2.5f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:113: Error: Value must be ≤ 7.0 (was 7.1) [Range]\n"
+ " printAtMostInclusive(7.1f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:117: Error: Value must be < 7.0 (was 7.0) [Range]\n"
+ " printAtMostExclusive(7.0f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:118: Error: Value must be < 7.0 (was 7.1) [Range]\n"
+ " printAtMostExclusive(7.1f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:120: Error: Value must be ≥ 2.5 (was 2.4) [Range]\n"
+ " printBetweenFromInclusiveToInclusive(2.4f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:124: Error: Value must be ≤ 5.0 (was 5.1) [Range]\n"
+ " printBetweenFromInclusiveToInclusive(5.1f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:126: Error: Value must be > 2.5 (was 2.4) [Range]\n"
+ " printBetweenFromExclusiveToInclusive(2.4f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:127: Error: Value must be > 2.5 (was 2.5) [Range]\n"
+ " printBetweenFromExclusiveToInclusive(2.5f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:129: Error: Value must be ≤ 5.0 (was 5.1) [Range]\n"
+ " printBetweenFromExclusiveToInclusive(5.1f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:131: Error: Value must be ≥ 2.5 (was 2.4) [Range]\n"
+ " printBetweenFromInclusiveToExclusive(2.4f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:135: Error: Value must be < 5.0 (was 5.0) [Range]\n"
+ " printBetweenFromInclusiveToExclusive(5.0f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:137: Error: Value must be > 2.5 (was 2.4) [Range]\n"
+ " printBetweenFromExclusiveToExclusive(2.4f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:138: Error: Value must be > 2.5 (was 2.5) [Range]\n"
+ " printBetweenFromExclusiveToExclusive(2.5f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:141: Error: Value must be < 5.0 (was 5.0) [Range]\n"
+ " printBetweenFromExclusiveToExclusive(5.0f); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/RangeTest.java:145: Error: Value must be ≥ 4 (was -7) [Range]\n"
+ " printBetween(-7); // ERROR\n"
+ " ~~\n"
+ "src/test/pkg/RangeTest.java:146: Error: Value must be > 2.5 (was -10.0) [Range]\n"
+ " printAtLeastExclusive(-10.0f); // ERROR\n"
+ " ~~~~~~\n"
+ "src/test/pkg/RangeTest.java:156: Error: Value must be ≥ -1 (was -2) [Range]\n"
+ " printIndirect(-2); // ERROR\n"
+ " ~~\n"
+ "src/test/pkg/RangeTest.java:157: Error: Value must be ≤ 42 (was 43) [Range]\n"
+ " printIndirect(43); // ERROR\n"
+ " ~~\n"
+ "src/test/pkg/RangeTest.java:158: Error: Expected length 5 (was 7) [Range]\n"
+ " printIndirectSize(\"1234567\"); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "41 errors, 0 warnings\n",
lintProject("src/test/pkg/RangeTest.java.txt=>src/test/pkg/RangeTest.java",
"src/android/support/annotation/Size.java.txt=>src/android/support/annotation/Size.java",
"src/android/support/annotation/IntRange.java.txt=>src/android/support/annotation/IntRange.java",
"src/android/support/annotation/FloatRange.java.txt=>src/android/support/annotation/FloatRange.java"
));
}
public void testTypeDef() throws Exception {
assertEquals(""
+ "src/test/pkg/IntDefTest.java:31: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setStyle(0, 0); // ERROR\n"
+ " ~\n"
+ "src/test/pkg/IntDefTest.java:32: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setStyle(-1, 0); // ERROR\n"
+ " ~~\n"
+ "src/test/pkg/IntDefTest.java:33: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setStyle(UNRELATED, 0); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:34: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setStyle(IntDefTest.UNRELATED, 0); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:35: Error: Flag not allowed here [WrongConstant]\n"
+ " setStyle(IntDefTest.STYLE_NORMAL|STYLE_NO_FRAME, 0); // ERROR: Not a flag\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:36: Error: Flag not allowed here [WrongConstant]\n"
+ " setStyle(~STYLE_NO_FRAME, 0); // ERROR: Not a flag\n"
+ " ~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:55: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setFlags(\"\", UNRELATED); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:56: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setFlags(\"\", UNRELATED|STYLE_NO_TITLE); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:57: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setFlags(\"\", STYLE_NORMAL|STYLE_NO_TITLE|UNRELATED); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:58: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setFlags(\"\", 1); // ERROR\n"
+ " ~\n"
+ "src/test/pkg/IntDefTest.java:59: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setFlags(\"\", arg < 0 ? STYLE_NORMAL : UNRELATED); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:60: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setFlags(\"\", arg < 0 ? UNRELATED : STYLE_NORMAL); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:79: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
+ " setTitle(\"\", UNRELATED_TYPE); // ERROR\n"
+ " ~~~~~~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:80: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
+ " setTitle(\"\", \"type2\"); // ERROR\n"
+ " ~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:87: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
+ " setTitle(\"\", type); // ERROR\n"
+ " ~~~~\n"
+ "src/test/pkg/IntDefTest.java:92: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ " setFlags(\"\", flag); // ERROR\n"
+ " ~~~~\n"
+ (SDK_ANNOTATIONS_AVAILABLE ?
"src/test/pkg/IntDefTest.java:99: Error: Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_INHERIT, View.LAYOUT_DIRECTION_LOCALE [WrongConstant]\n"
+ " view.setLayoutDirection(View.TEXT_DIRECTION_LTR); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:100: Error: Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_INHERIT, View.LAYOUT_DIRECTION_LOCALE [WrongConstant]\n"
+ " view.setLayoutDirection(0); // ERROR\n"
+ " ~\n"
+ "src/test/pkg/IntDefTest.java:101: Error: Flag not allowed here [WrongConstant]\n"
+ " view.setLayoutDirection(View.LAYOUT_DIRECTION_LTR|View.LAYOUT_DIRECTION_RTL); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/IntDefTest.java:102: Error: Must be one of: Context.POWER_SERVICE, Context.WINDOW_SERVICE, Context.LAYOUT_INFLATER_SERVICE, Context.ACCOUNT_SERVICE, Context.ACTIVITY_SERVICE, Context.ALARM_SERVICE, Context.NOTIFICATION_SERVICE, Context.ACCESSIBILITY_SERVICE, Context.CAPTIONING_SERVICE, Context.KEYGUARD_SERVICE, Context.LOCATION_SERVICE, Context.SEARCH_SERVICE, Context.SENSOR_SERVICE, Context.STORAGE_SERVICE, Context.WALLPAPER_SERVICE, Context.VIBRATOR_SERVICE, Context.CONNECTIVITY_SERVICE, Context.NETWORK_STATS_SERVICE, Context.WIFI_SERVICE, Context.WIFI_P2P_SERVICE, Context.NSD_SERVICE, Context.AUDIO_SERVICE, Context.FINGERPRINT_SERVICE, Context.MEDIA_ROUTER_SERVICE, Context.TELEPHONY_SERVICE, Context.TELEPHONY_SUBSCRIPTION_SERVICE, Context.CARRIER_CONFIG_SERVICE, Context.TELECOM_SERVICE, Context.CLIPBOARD_SERVICE, Context.INPUT_METHOD_SERVICE, Context.TEXT_SERVICES_MANAGER_SERVICE, Context.APPWIDGET_SERVICE, Context.DROPBOX_SERVICE, Context.DEVICE_POLICY_SERVICE, Context.UI_MODE_SERVICE, Context.DOWNLOAD_SERVICE, Context.NFC_SERVICE, Context.BLUETOOTH_SERVICE, Context.USB_SERVICE, Context.LAUNCHER_APPS_SERVICE, Context.INPUT_SERVICE, Context.DISPLAY_SERVICE, Context.USER_SERVICE, Context.RESTRICTIONS_SERVICE, Context.APP_OPS_SERVICE, Context.CAMERA_SERVICE, Context.PRINT_SERVICE, Context.CONSUMER_IR_SERVICE, Context.TV_INPUT_SERVICE, Context.USAGE_STATS_SERVICE, Context.MEDIA_SESSION_SERVICE, Context.BATTERY_SERVICE, Context.JOB_SCHEDULER_SERVICE, Context.MEDIA_PROJECTION_SERVICE, Context.MIDI_SERVICE [WrongConstant]\n"
+ " context.getSystemService(TYPE_1); // ERROR\n"
+ " ~~~~~~\n"
+ "20 errors, 0 warnings\n" :
"16 errors, 0 warnings\n"),
lintProject("src/test/pkg/IntDefTest.java.txt=>src/test/pkg/IntDefTest.java",
"src/android/support/annotation/IntDef.java.txt=>src/android/support/annotation/IntDef.java",
"src/android/support/annotation/StringDef.java.txt=>src/android/support/annotation/StringDef.java"
));
}
public void testColorInt() throws Exception {
// Needs updated annotations!
assertEquals((SDK_ANNOTATIONS_AVAILABLE ? ""
+ "src/test/pkg/WrongColor.java:9: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
+ " paint2.setColor(R.color.blue);\n"
+ " ~~~~~~~~~~~~\n"
+ "src/test/pkg/WrongColor.java:11: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.red) [ResourceAsColor]\n"
+ " textView.setTextColor(R.color.red);\n"
+ " ~~~~~~~~~~~\n"
+ "src/test/pkg/WrongColor.java:12: Error: Should pass resolved color instead of resource id here: getResources().getColor(android.R.color.black) [ResourceAsColor]\n"
+ " textView.setTextColor(android.R.color.black);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/WrongColor.java:13: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
+ " textView.setTextColor(foo > 0 ? R.color.green : R.color.blue);\n"
+ " ~~~~~~~~~~~~\n"
+ "src/test/pkg/WrongColor.java:13: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.green) [ResourceAsColor]\n"
+ " textView.setTextColor(foo > 0 ? R.color.green : R.color.blue);\n"
+ " ~~~~~~~~~~~~~\n" : "")
+ "src/test/pkg/WrongColor.java:21: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
+ " foo2(R.color.blue);\n"
+ " ~~~~~~~~~~~~\n"
+ "src/test/pkg/WrongColor.java:20: Error: Expected resource of type color [ResourceType]\n"
+ " foo1(0xffff0000);\n"
+ " ~~~~~~~~~~\n"
+ (SDK_ANNOTATIONS_AVAILABLE ? "7 errors, 0 warnings\n" : "2 errors, 0 warnings\n"),
lintProject(
copy("src/test/pkg/WrongColor.java.txt", "src/test/pkg/WrongColor.java"),
copy("src/android/support/annotation/ColorInt.java.txt", "src/android/support/annotation/ColorInt.java"),
mColorResAnnotation
));
}
public void testColorInt2() throws Exception {
assertEquals(""
+ "src/test/pkg/ColorTest.java:23: Error: Should pass resolved color instead of resource id here: getResources().getColor(actualColor) [ResourceAsColor]\n"
+ " setColor2(actualColor); // ERROR\n"
+ " ~~~~~~~~~~~\n"
+ "src/test/pkg/ColorTest.java:24: Error: Should pass resolved color instead of resource id here: getResources().getColor(getColor2()) [ResourceAsColor]\n"
+ " setColor2(getColor2()); // ERROR\n"
+ " ~~~~~~~~~~~\n"
+ "src/test/pkg/ColorTest.java:17: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
+ " setColor1(actualColor); // ERROR\n"
+ " ~~~~~~~~~~~\n"
+ "src/test/pkg/ColorTest.java:18: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
+ " setColor1(getColor1()); // ERROR\n"
+ " ~~~~~~~~~~~\n"
+ "4 errors, 0 warnings\n",
lintProject(
java("src/test/pkg/ColorTest.java", ""
+ "package test.pkg;\n"
+ "import android.content.Context;\n"
+ "import android.content.res.Resources;\n"
+ "import android.support.annotation.ColorInt;\n"
+ "import android.support.annotation.ColorRes;\n"
+ "\n"
+ "public abstract class ColorTest {\n"
+ " @ColorInt\n"
+ " public abstract int getColor1();\n"
+ " public abstract void setColor1(@ColorRes int color);\n"
+ " @ColorRes\n"
+ " public abstract int getColor2();\n"
+ " public abstract void setColor2(@ColorInt int color);\n"
+ "\n"
+ " public void test1(Context context) {\n"
+ " int actualColor = getColor1();\n"
+ " setColor1(actualColor); // ERROR\n"
+ " setColor1(getColor1()); // ERROR\n"
+ " setColor1(getColor2()); // OK\n"
+ " }\n"
+ " public void test2(Context context) {\n"
+ " int actualColor = getColor2();\n"
+ " setColor2(actualColor); // ERROR\n"
+ " setColor2(getColor2()); // ERROR\n"
+ " setColor2(getColor1()); // OK\n"
+ " }\n"
+ "}\n"),
mColorResAnnotation,
mColorIntAnnotation
));
}
public void testColorInt3() throws Exception {
// Regression test for https://code.google.com/p/android/issues/detail?id=176321
if (!SDK_ANNOTATIONS_AVAILABLE) {
return;
}
assertEquals(""
+ "src/test/pkg/ColorTest.java:11: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
+ " setColor(actualColor);\n"
+ " ~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
java("src/test/pkg/ColorTest.java", ""
+ "package test.pkg;\n"
+ "import android.content.Context;\n"
+ "import android.content.res.Resources;\n"
+ "import android.support.annotation.ColorRes;\n"
+ "\n"
+ "public abstract class ColorTest {\n"
+ " public abstract void setColor(@ColorRes int color);\n"
+ "\n"
+ " public void test(Context context, @ColorRes int id) {\n"
+ " int actualColor = context.getResources().getColor(id, null);\n"
+ " setColor(actualColor);\n"
+ " }\n"
+ "}\n"),
mColorResAnnotation
));
}
public void testResourceType() throws Exception {
assertEquals((SDK_ANNOTATIONS_AVAILABLE ? ""
+ "src/p1/p2/Flow.java:13: Error: Expected resource of type drawable [ResourceType]\n"
+ " resources.getDrawable(10); // ERROR\n"
+ " ~~\n"
+ "src/p1/p2/Flow.java:18: Error: Expected resource of type drawable [ResourceType]\n"
+ " resources.getDrawable(R.string.my_string); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~\n" : "")
+ "src/p1/p2/Flow.java:22: Error: Expected resource of type drawable [ResourceType]\n"
+ " myMethod(R.string.my_string, null); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~\n"
+ "src/p1/p2/Flow.java:26: Error: Expected resource of type drawable [ResourceType]\n"
+ " resources.getDrawable(R.string.my_string); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~\n"
+ "src/p1/p2/Flow.java:32: Error: Expected resource identifier (R.type.name) [ResourceType]\n"
+ " myAnyResMethod(50); // ERROR\n"
+ " ~~\n"
+ (SDK_ANNOTATIONS_AVAILABLE ? "src/p1/p2/Flow.java:60: Error: Expected resource of type drawable [ResourceType]\n"
+ " resources.getDrawable(MimeTypes.getAnnotatedString()); // Error\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" : "")
+ "src/p1/p2/Flow.java:68: Error: Expected resource of type drawable [ResourceType]\n"
+ " myMethod(z, null); // ERROR\n"
+ " ~\n"
+ (SDK_ANNOTATIONS_AVAILABLE ? "7 errors, 0 warnings\n" : "4 errors, 0 warnings\n"),
lintProject(
copy("src/p1/p2/Flow.java.txt", "src/p1/p2/Flow.java"),
copy("src/android/support/annotation/DrawableRes.java.txt", "src/android/support/annotation/DrawableRes.java"),
mStringResAnnotation,
mStyleResAnnotation,
mAnyResAnnotation
));
}
public void testTypes2() throws Exception {
assertEquals(""
+ "src/test/pkg/ActivityType.java:5: Error: Expected resource of type drawable [ResourceType]\n"
+ " SKI(1),\n"
+ " ~\n"
+ "src/test/pkg/ActivityType.java:6: Error: Expected resource of type drawable [ResourceType]\n"
+ " SNOWBOARD(2);\n"
+ " ~\n"
+ "2 errors, 0 warnings\n",
lintProject(
java("src/test/pkg/ActivityType.java", ""
+ "import android.support.annotation.DrawableRes;\n"
+ "\n"
+ "enum ActivityType {\n"
+ "\n"
+ " SKI(1),\n"
+ " SNOWBOARD(2);\n"
+ "\n"
+ " private final int mIconResId;\n"
+ "\n"
+ " ActivityType(@DrawableRes int iconResId) {\n"
+ " mIconResId = iconResId;\n"
+ " }\n"
+ "}"),
copy("src/android/support/annotation/DrawableRes.java.txt",
"src/android/support/annotation/DrawableRes.java")));
}
@SuppressWarnings({"MethodMayBeStatic", "ResultOfObjectAllocationIgnored"})
public void testConstructor() throws Exception {
assertEquals(""
+ "src/test/pkg/ConstructorTest.java:14: Error: Expected resource of type drawable [ResourceType]\n"
+ " new ConstructorTest(1, 3);\n"
+ " ~\n"
+ "src/test/pkg/ConstructorTest.java:14: Error: Value must be ≥ 5 (was 3) [Range]\n"
+ " new ConstructorTest(1, 3);\n"
+ " ~\n"
+ "src/test/pkg/ConstructorTest.java:19: Error: Method test.pkg.ConstructorTest must be called from the UI thread, currently inferred thread is worker thread [WrongThread]\n"
+ " new ConstructorTest(res, range);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "3 errors, 0 warnings\n",
lintProject(
java("src/test/pkg/ConstructorTest.java", ""
+ "package test.pkg;\n"
+ "\n"
+ "import android.support.annotation.DrawableRes;\n"
+ "import android.support.annotation.IntRange;\n"
+ "import android.support.annotation.UiThread;\n"
+ "import android.support.annotation.WorkerThread;\n"
+ "\n"
+ "public class ConstructorTest {\n"
+ " @UiThread\n"
+ " ConstructorTest(@DrawableRes int iconResId, @IntRange(from = 5) int start) {\n"
+ " }\n"
+ "\n"
+ " public void testParameters() {\n"
+ " new ConstructorTest(1, 3);\n"
+ " }\n"
+ "\n"
+ " @WorkerThread\n"
+ " public void testMethod(int res, int range) {\n"
+ " new ConstructorTest(res, range);\n"
+ " }\n"
+ "}\n"),
mWorkerThreadPermission,
mUiThreadPermission,
copy("src/android/support/annotation/DrawableRes.java.txt",
"src/android/support/annotation/DrawableRes.java"),
copy("src/android/support/annotation/IntRange.java.txt",
"src/android/support/annotation/IntRange.java")
));
}
public void testColorAsDrawable() throws Exception {
assertEquals(
"No warnings.",
lintProject("src/p1/p2/ColorAsDrawable.java.txt=>src/p1/p2/ColorAsDrawable.java"));
}
public void testCheckResult() throws Exception {
if (!SDK_ANNOTATIONS_AVAILABLE) {
// Currently only tests @CheckResult on SDK annotations
return;
}
assertEquals(""
+ "src/test/pkg/CheckPermissions.java:22: Warning: The result of extractAlpha is not used [CheckResult]\n"
+ " bitmap.extractAlpha(); // WARNING\n"
+ " ~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/CheckPermissions.java:10: Warning: The result of checkCallingOrSelfPermission is not used; did you mean to call #enforceCallingOrSelfPermission(String,String)? [UseCheckPermission]\n"
+ " context.checkCallingOrSelfPermission(Manifest.permission.INTERNET); // WRONG\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/CheckPermissions.java:11: Warning: The result of checkPermission is not used; did you mean to call #enforcePermission(String,int,int,String)? [UseCheckPermission]\n"
+ " context.checkPermission(Manifest.permission.INTERNET, 1, 1);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 3 warnings\n",
lintProject("src/test/pkg/CheckPermissions.java.txt=>src/test/pkg/CheckPermissions.java"));
}
private final TestFile mPermissionTest = java("src/test/pkg/PermissionTest.java", ""
+ "package test.pkg;\n"
+ "\n"
+ "import android.location.LocationManager;\n"
+ "\n"
+ "public class PermissionTest {\n"
+ " public static void test(LocationManager locationManager, String provider) {\n"
+ " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ " }\n"
+ "}\n");
private final TestFile mLocationManagerStub = java("src/android/location/LocationManager.java", ""
+ "package android.location;\n"
+ "\n"
+ "import android.support.annotation.RequiresPermission;\n"
+ "\n"
+ "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
+ "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
+ "\n"
+ "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ "public abstract class LocationManager {\n"
+ " @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})\n"
+ " public abstract Location myMethod(String provider);\n"
+ " public static class Location {\n"
+ " }\n"
+ "}\n");
private final TestFile mComplexLocationManagerStub = java("src/android/location/LocationManager.java", ""
+ "package android.location;\n"
+ "\n"
+ "import android.support.annotation.RequiresPermission;\n"
+ "\n"
+ "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
+ "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
+ "import static android.Manifest.permission.BLUETOOTH;\n"
+ "import static android.Manifest.permission.READ_SMS;\n"
+ "\n"
+ "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ "public abstract class LocationManager {\n"
+ " @RequiresPermission(\"(\" + ACCESS_FINE_LOCATION + \"|| \" + ACCESS_COARSE_LOCATION + \") && (\" + BLUETOOTH + \" ^ \" + READ_SMS + \")\")\n"
+ " public abstract Location myMethod(String provider);\n"
+ " public static class Location {\n"
+ " }\n"
+ "}\n");
private final TestFile mRequirePermissionAnnotation = java("src/android/support/annotation/RequiresPermission.java", ""
+ "/*\n"
+ " * Copyright (C) 2015 The Android Open Source Project\n"
+ " *\n"
+ " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+ " * you may not use this file except in compliance with the License.\n"
+ " * You may obtain a copy of the License at\n"
+ " *\n"
+ " * http://www.apache.org/licenses/LICENSE-2.0\n"
+ " *\n"
+ " * Unless required by applicable law or agreed to in writing, software\n"
+ " * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+ " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+ " * See the License for the specific language governing permissions and\n"
+ " * limitations under the License.\n"
+ " */\n"
+ "package android.support.annotation;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.Target;\n"
+ "\n"
+ "import static java.lang.annotation.ElementType.*;\n"
+ "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ "@Retention(CLASS)\n"
+ "@Target({METHOD,CONSTRUCTOR,FIELD,PARAMETER,ANNOTATION_TYPE})\n"
+ "public @interface RequiresPermission {\n"
+ " String value() default \"\";\n"
+ " String[] allOf() default {};\n"
+ " String[] anyOf() default {};\n"
+ " boolean conditional() default false;\n"
+ " String notes() default \"\";\n"
+ " @Target({FIELD,METHOD,PARAMETER})\n"
+ " @interface Read {\n"
+ " RequiresPermission value();\n"
+ " }\n"
+ " @Target({FIELD,METHOD,PARAMETER})\n"
+ " @interface Write {\n"
+ " RequiresPermission value();\n"
+ " }\n"
+ "}");
private final TestFile mUiThreadPermission = java("src/android/support/annotation/UiThread.java", ""
+ "package android.support.annotation;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.Target;\n"
+ "\n"
+ "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
+ "import static java.lang.annotation.ElementType.METHOD;\n"
+ "import static java.lang.annotation.ElementType.TYPE;\n"
+ "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ "\n"
+ "@Retention(CLASS)\n"
+ "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
+ "public @interface UiThread {\n"
+ "}\n");
private final TestFile mMainThreadPermission = java("src/android/support/annotation/MainThread.java", ""
+ "package android.support.annotation;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.Target;\n"
+ "\n"
+ "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
+ "import static java.lang.annotation.ElementType.METHOD;\n"
+ "import static java.lang.annotation.ElementType.TYPE;\n"
+ "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ "\n"
+ "@Retention(CLASS)\n"
+ "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
+ "public @interface MainThread {\n"
+ "}\n");
private final TestFile mWorkerThreadPermission = java("src/android/support/annotation/WorkerThread.java", ""
+ "package android.support.annotation;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.Target;\n"
+ "\n"
+ "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
+ "import static java.lang.annotation.ElementType.METHOD;\n"
+ "import static java.lang.annotation.ElementType.TYPE;\n"
+ "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ "\n"
+ "@Retention(CLASS)\n"
+ "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
+ "public @interface WorkerThread {\n"
+ "}\n");
private TestFile createResAnnotation(String prefix) {
return java("src/android/support/annotation/" + prefix + "Res.java", ""
+ "package android.support.annotation;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.Target;\n"
+ "\n"
+ "import static java.lang.annotation.ElementType.*;\n"
+ "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ "\n"
+ "@Retention(CLASS)\n"
+ "@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n"
+ "public @interface " + prefix + "Res {\n"
+ "}\n");
}
private final TestFile mColorResAnnotation = createResAnnotation("Color");
private final TestFile mStringResAnnotation = createResAnnotation("String");
private final TestFile mStyleResAnnotation = createResAnnotation("Style");
private final TestFile mAnyResAnnotation = createResAnnotation("Any");
private final TestFile mColorIntAnnotation = java("src/android/support/annotation/ColorInt.java", ""
+ "package android.support.annotation;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.Target;\n"
+ "\n"
+ "import static java.lang.annotation.ElementType.*;\n"
+ "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ "\n"
+ "@Retention(CLASS)\n"
+ "@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n"
+ "public @interface ColorInt {\n"
+ "}\n");
private TestFile getManifestWithPermissions(int targetSdk, String... permissions) {
return getManifestWithPermissions(1, targetSdk, permissions);
}
private TestFile getManifestWithPermissions(int minSdk, int targetSdk, String... permissions) {
StringBuilder permissionBlock = new StringBuilder();
for (String permission : permissions) {
permissionBlock.append(" <uses-permission android:name=\"").append(permission)
.append("\" />\n");
}
return xml("AndroidManifest.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"foo.bar2\"\n"
+ " android:versionCode=\"1\"\n"
+ " android:versionName=\"1.0\" >\n"
+ "\n"
+ " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\""
+ targetSdk + "\" />\n"
+ "\n"
+ permissionBlock.toString()
+ "\n"
+ " <application\n"
+ " android:icon=\"@drawable/ic_launcher\"\n"
+ " android:label=\"@string/app_name\" >\n"
+ " </application>\n"
+ "\n"
+ "</manifest>");
}
private TestFile mRevokeTest = java("src/test/pkg/RevokeTest.java", ""
+ "package test.pkg;\n"
+ "\n"
+ "import android.content.Context;\n"
+ "import android.content.pm.PackageManager;\n"
+ "import android.location.LocationManager;\n"
+ "\n"
+ "import java.security.AccessControlException;\n"
+ "\n"
+ "public class RevokeTest {\n"
+ " public static void test1(LocationManager locationManager, String provider) {\n"
+ " try {\n"
+ " // Ok: Security exception caught in one of the branches\n"
+ " locationManager.myMethod(provider); // OK\n"
+ " } catch (IllegalArgumentException ignored) {\n"
+ " } catch (SecurityException ignored) {\n"
+ " }\n"
+ "\n"
+ " try {\n"
+ " // Ok: Security exception super class caught in one of the branches\n"
+ " locationManager.myMethod(provider); // OK\n"
+ " } catch (RuntimeException e) { // includes Security Exception\n"
+ " }\n"
+ "\n"
+ " try {\n"
+ " // Ok: Caught in outer statement\n"
+ " try {\n"
+ " locationManager.myMethod(provider); // OK\n"
+ " } catch (IllegalArgumentException e) {\n"
+ " // inner\n"
+ " }\n"
+ " } catch (SecurityException ignored) {\n"
+ " }\n"
+ "\n"
+ " try {\n"
+ " // Ok: Security exception super class caught in one of the branches\n"
+ " locationManager.myMethod(provider); // OK\n"
+ " } catch (Exception e) { // includes Security Exception\n"
+ " }\n"
+ "\n"
+ " // NOT OK: Catching security exception subclass (except for dedicated ones?)\n"
+ "\n"
+ " try {\n"
+ " // Error: catching security exception, but not all of them\n"
+ " locationManager.myMethod(provider); // ERROR\n"
+ " } catch (AccessControlException e) { // security exception but specific one\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " public static void test2(LocationManager locationManager, String provider) {\n"
+ " locationManager.myMethod(provider); // ERROR: not caught\n"
+ " }\n"
+ "\n"
+ " public static void test3(LocationManager locationManager, String provider)\n"
+ " throws IllegalArgumentException {\n"
+ " locationManager.myMethod(provider); // ERROR: not caught by right type\n"
+ " }\n"
+ "\n"
+ " public static void test4(LocationManager locationManager, String provider)\n"
+ " throws AccessControlException { // Security exception but specific one\n"
+ " locationManager.myMethod(provider); // ERROR\n"
+ " }\n"
+ "\n"
+ " public static void test5(LocationManager locationManager, String provider)\n"
+ " throws SecurityException {\n"
+ " locationManager.myMethod(provider); // OK\n"
+ " }\n"
+ "\n"
+ " public static void test6(LocationManager locationManager, String provider)\n"
+ " throws Exception { // includes Security Exception\n"
+ " locationManager.myMethod(provider); // OK\n"
+ " }\n"
+ "\n"
+ " public static void test7(LocationManager locationManager, String provider, Context context)\n"
+ " throws IllegalArgumentException {\n"
+ " if (context.getPackageManager().checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, context.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n"
+ " return;\n"
+ " }\n"
+ " locationManager.myMethod(provider); // OK: permission checked\n"
+ " }\n"
+ "\n"
+ "}\n");
public void testMissingPermissions() throws Exception {
assertEquals(""
+ "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION [MissingPermission]\n"
+ " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(14),
mPermissionTest,
mLocationManagerStub,
mRequirePermissionAnnotation));
}
public void testHasPermission() throws Exception {
assertEquals("No warnings.",
lintProject(
getManifestWithPermissions(14, "android.permission.ACCESS_FINE_LOCATION"),
mPermissionTest,
mLocationManagerStub,
mRequirePermissionAnnotation));
}
public void testRevokePermissions() throws Exception {
assertEquals(""
+ "src/test/pkg/RevokeTest.java:44: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
+ " locationManager.myMethod(provider); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RevokeTest.java:50: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
+ " locationManager.myMethod(provider); // ERROR: not caught\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RevokeTest.java:55: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
+ " locationManager.myMethod(provider); // ERROR: not caught by right type\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/RevokeTest.java:60: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
+ " locationManager.myMethod(provider); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "4 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(23, "android.permission.ACCESS_FINE_LOCATION"),
mLocationManagerStub,
mRequirePermissionAnnotation,
mRevokeTest
));
}
public void testImpliedPermissions() throws Exception {
// Regression test for
// https://code.google.com/p/android/issues/detail?id=177381
assertEquals(""
+ "src/test/pkg/PermissionTest2.java:11: Error: Missing permissions required by PermissionTest2.method1: my.permission.PERM2 [MissingPermission]\n"
+ " method1(); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(14, 14, "android.permission.ACCESS_FINE_LOCATION"),
java("src/test/pkg/PermissionTest2.java", ""
+ "package test.pkg;\n"
+ "import android.support.annotation.RequiresPermission;\n"
+ "\n"
+ "public class PermissionTest2 {\n"
+ " @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n"
+ " public void method1() {\n"
+ " }\n"
+ "\n"
+ " @RequiresPermission(\"my.permission.PERM1\")\n"
+ " public void method2() {\n"
+ " method1(); // ERROR\n"
+ " }\n"
+ "\n"
+ " @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n"
+ " public void method3() {\n"
+ " // The above @RequiresPermission implies that we are holding these\n"
+ " // permissions here, so the call to method1() should not be flagged as\n"
+ " // missing a permission!\n"
+ " method1(); // OK\n"
+ " }\n"
+ "}\n"),
mRequirePermissionAnnotation
));
}
public void testRevokePermissionsPre23() throws Exception {
assertEquals("No warnings.",
lintProject(
getManifestWithPermissions(14, "android.permission.ACCESS_FINE_LOCATION"),
mLocationManagerStub,
mRequirePermissionAnnotation,
mRevokeTest
));
}
public void testComplexPermission1() throws Exception {
assertEquals(""
+ "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.BLUETOOTH xor android.permission.READ_SMS [MissingPermission]\n"
+ " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(14,
"android.permission.ACCESS_FINE_LOCATION"),
mPermissionTest,
mComplexLocationManagerStub,
mRequirePermissionAnnotation));
}
public void testComplexPermission2() throws Exception {
assertEquals("No warnings.",
lintProject(
getManifestWithPermissions(14,
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.BLUETOOTH"),
mPermissionTest,
mComplexLocationManagerStub,
mRequirePermissionAnnotation));
}
public void testComplexPermission3() throws Exception {
assertEquals(""
+ "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.BLUETOOTH xor android.permission.READ_SMS [MissingPermission]\n"
+ " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(14,
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.BLUETOOTH",
"android.permission.READ_SMS"),
mPermissionTest,
mComplexLocationManagerStub,
mRequirePermissionAnnotation));
}
public void testPermissionAnnotation() throws Exception {
assertEquals(""
+ "src/test/pkg/LocationManager.java:24: Error: Missing permissions required by LocationManager.getLastKnownLocation: android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION [MissingPermission]\n"
+ " Location location = manager.getLastKnownLocation(\"provider\");\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
java("src/test/pkg/LocationManager.java", ""
+ "package test.pkg;\n"
+ "\n"
+ "import android.support.annotation.RequiresPermission;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.RetentionPolicy;\n"
+ "\n"
+ "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
+ "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
+ "\n"
+ "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ "public abstract class LocationManager {\n"
+ " @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})\n"
+ " @Retention(RetentionPolicy.SOURCE)\n"
+ " @interface AnyLocationPermission {\n"
+ " }\n"
+ "\n"
+ " @AnyLocationPermission\n"
+ " public abstract Location getLastKnownLocation(String provider);\n"
+ " public static class Location {\n"
+ " }\n"
+ " \n"
+ " public static void test(LocationManager manager) {\n"
+ " Location location = manager.getLastKnownLocation(\"provider\");\n"
+ " }\n"
+ "}\n"),
mRequirePermissionAnnotation));
}
public void testThreading() throws Exception {
assertEquals(""
+ "src/test/pkg/ThreadTest.java:15: Error: Method onPreExecute must be called from the main thread, currently inferred thread is worker thread [WrongThread]\n"
+ " onPreExecute(); // ERROR\n"
+ " ~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ThreadTest.java:16: Error: Method paint must be called from the UI thread, currently inferred thread is worker thread [WrongThread]\n"
+ " view.paint(); // ERROR\n"
+ " ~~~~~~~~~~~~\n"
+ "src/test/pkg/ThreadTest.java:22: Error: Method publishProgress must be called from the worker thread, currently inferred thread is main thread [WrongThread]\n"
+ " publishProgress(); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~\n"
+ "3 errors, 0 warnings\n",
lintProject(
java("src/test/pkg/ThreadTest.java", ""
+ "package test.pkg;\n"
+ "\n"
+ "import android.support.annotation.MainThread;\n"
+ "import android.support.annotation.UiThread;\n"
+ "import android.support.annotation.WorkerThread;\n"
+ "\n"
+ "public class ThreadTest {\n"
+ " public static AsyncTask testTask() {\n"
+ "\n"
+ " return new AsyncTask() {\n"
+ " final CustomView view = new CustomView();\n"
+ "\n"
+ " @Override\n"
+ " protected void doInBackground(Object... params) {\n"
+ " onPreExecute(); // ERROR\n"
+ " view.paint(); // ERROR\n"
+ " publishProgress(); // OK\n"
+ " }\n"
+ "\n"
+ " @Override\n"
+ " protected void onPreExecute() {\n"
+ " publishProgress(); // ERROR\n"
+ " onProgressUpdate(); // OK\n"
+ " }\n"
+ " };\n"
+ " }\n"
+ "\n"
+ " @UiThread\n"
+ " public static class View {\n"
+ " public void paint() {\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " public static class CustomView extends View {\n"
+ " @Override public void paint() {\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " public abstract static class AsyncTask {\n"
+ " @WorkerThread\n"
+ " protected abstract void doInBackground(Object... params);\n"
+ "\n"
+ " @MainThread\n"
+ " protected void onPreExecute() {\n"
+ " }\n"
+ "\n"
+ " @MainThread\n"
+ " protected void onProgressUpdate(Object... values) {\n"
+ " }\n"
+ "\n"
+ " @WorkerThread\n"
+ " protected final void publishProgress(Object... values) {\n"
+ " }\n"
+ " }\n"
+ "}\n"),
mUiThreadPermission,
mMainThreadPermission,
mWorkerThreadPermission));
}
public void testIntentPermission() throws Exception {
if (SDK_ANNOTATIONS_AVAILABLE) {
TestLintClient client = createClient();
ExternalAnnotationRepository repository = ExternalAnnotationRepository.get(client);
ResolvedMethod method = ExternalAnnotationRepositoryTest.createMethod(
"android.content.Context", "void", "startActivity",
"android.content.Intent");
ResolvedAnnotation a = repository.getAnnotation(method, 0, PERMISSION_ANNOTATION);
if (a == null) {
// Running tests from outside the IDE (where it can't find the
// bundled up to date annotations in tools/adt/idea/android/annotations)
// and we have the annotations.zip file available in platform-tools,
// but its contents are old (it's from Android M Preview 1, not including
// the new intent-annotation data); skip this test for now.
return;
}
}
assertEquals(!SDK_ANNOTATIONS_AVAILABLE ? "" // Most of the intent/content provider checks are based on framework annotations
+ "src/test/pkg/ActionTest.java:86: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:87: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:88: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " myWriteResolverMethod(BOOKMARKS_URI);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "3 errors, 0 warnings\n" : ""
+ "src/test/pkg/ActionTest.java:36: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " activity.startActivity(intent);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:42: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " activity.startActivity(intent);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:43: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " activity.startActivity(intent, null);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:44: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " activity.startActivityForResult(intent, 0);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:45: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " activity.startActivityFromChild(activity, intent, 0);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:46: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " activity.startActivityIfNeeded(intent, 0);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:47: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " activity.startActivityFromFragment(null, intent, 0);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:48: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " activity.startNextMatchingActivity(intent);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:54: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " context.sendBroadcast(intent);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:55: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " context.sendBroadcast(intent, \"\");\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:56: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " context.sendBroadcastAsUser(intent, null);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:57: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " context.sendStickyBroadcast(intent);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:62: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " resolver.query(BOOKMARKS_URI, null, null, null, null);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:65: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " resolver.insert(BOOKMARKS_URI, null);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:66: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " resolver.delete(BOOKMARKS_URI, null, null);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:67: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " resolver.update(BOOKMARKS_URI, null, null, null);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:86: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:87: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:88: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " myWriteResolverMethod(BOOKMARKS_URI);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "19 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(14, 23),
java("src/test/pkg/ActionTest.java", ""
+ "package test.pkg;\n"
+ "\n"
+ "import android.Manifest;\n"
+ "import android.app.Activity;\n"
+ "import android.content.ContentResolver;\n"
+ "import android.content.Context;\n"
+ "import android.content.Intent;\n"
+ "import android.database.Cursor;\n"
+ "import android.net.Uri;\n"
+ "import android.support.annotation.RequiresPermission;\n"
+ "\n"
//+ "import static android.Manifest.permission.READ_HISTORY_BOOKMARKS;\n"
//+ "import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS;\n"
+ "\n"
+ "@SuppressWarnings({\"deprecation\", \"unused\"})\n"
+ "public class ActionTest {\n"
+ " public static final String READ_HISTORY_BOOKMARKS=\"com.android.browser.permission.READ_HISTORY_BOOKMARKS\";\n"
+ " public static final String WRITE_HISTORY_BOOKMARKS=\"com.android.browser.permission.WRITE_HISTORY_BOOKMARKS\";\n"
+ " @RequiresPermission(Manifest.permission.CALL_PHONE)\n"
+ " public static final String ACTION_CALL = \"android.intent.action.CALL\";\n"
+ "\n"
+ " @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))\n"
+ " @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))\n"
+ " public static final Uri BOOKMARKS_URI = Uri.parse(\"content://browser/bookmarks\");\n"
+ "\n"
+ " public static final Uri COMBINED_URI = Uri.withAppendedPath(BOOKMARKS_URI, \"bookmarks\");\n"
+ " \n"
+ " public static void activities1(Activity activity) {\n"
+ " Intent intent = new Intent(Intent.ACTION_CALL);\n"
+ " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
+ " // This one will only be flagged if we have framework metadata on Intent.ACTION_CALL\n"
+ " activity.startActivity(intent);\n"
+ " }\n"
+ "\n"
+ " public static void activities2(Activity activity) {\n"
+ " Intent intent = new Intent(ACTION_CALL);\n"
+ " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
+ " activity.startActivity(intent);\n"
+ " }\n"
+ " public static void activities3(Activity activity) {\n"
+ " Intent intent;\n"
+ " intent = new Intent(ACTION_CALL);\n"
+ " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
+ " activity.startActivity(intent);\n"
+ " activity.startActivity(intent, null);\n"
+ " activity.startActivityForResult(intent, 0);\n"
+ " activity.startActivityFromChild(activity, intent, 0);\n"
+ " activity.startActivityIfNeeded(intent, 0);\n"
+ " activity.startActivityFromFragment(null, intent, 0);\n"
+ " activity.startNextMatchingActivity(intent);\n"
+ " }\n"
+ "\n"
+ " public static void broadcasts(Context context) {\n"
+ " Intent intent;\n"
+ " intent = new Intent(ACTION_CALL);\n"
+ " context.sendBroadcast(intent);\n"
+ " context.sendBroadcast(intent, \"\");\n"
+ " context.sendBroadcastAsUser(intent, null);\n"
+ " context.sendStickyBroadcast(intent);\n"
+ " }\n"
+ "\n"
+ " public static void contentResolvers(Context context, ContentResolver resolver) {\n"
+ " // read\n"
+ " resolver.query(BOOKMARKS_URI, null, null, null, null);\n"
+ "\n"
+ " // write\n"
+ " resolver.insert(BOOKMARKS_URI, null);\n"
+ " resolver.delete(BOOKMARKS_URI, null, null);\n"
+ " resolver.update(BOOKMARKS_URI, null, null, null);\n"
+ "\n"
+ " // Framework (external) annotation\n"
+ "//REMOVED resolver.query(android.provider.Browser.BOOKMARKS_URI, null, null, null, null);\n"
+ "\n"
+ " // TODO: Look for more complex URI manipulations\n"
+ " }\n"
+ "\n"
+ " public static void myStartActivity(String s1, String s2, \n"
+ " @RequiresPermission Intent intent) {\n"
+ " }\n"
+ "\n"
+ " public static void myReadResolverMethod(String s1, @RequiresPermission.Read(@RequiresPermission) Uri uri) {\n"
+ " }\n"
+ "\n"
+ " public static void myWriteResolverMethod(@RequiresPermission.Read(@RequiresPermission) Uri uri) {\n"
+ " }\n"
+ " \n"
+ " public static void testCustomMethods() {\n"
+ " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
+ " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
+ " myWriteResolverMethod(BOOKMARKS_URI);\n"
+ " }\n"
+ "}\n"),
mRequirePermissionAnnotation
));
}
public void testCombinedIntDefAndIntRange() throws Exception {
assertEquals(""
+ "src/test/pkg/X.java:27: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG [WrongConstant]\n"
+ " setDuration(UNRELATED); /// ERROR: Not right intdef, even if it's in the right number range\n"
+ " ~~~~~~~~~\n"
+ "src/test/pkg/X.java:28: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was -5) [WrongConstant]\n"
+ " setDuration(-5); // ERROR (not right int def or value\n"
+ " ~~\n"
+ "src/test/pkg/X.java:29: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was 8) [WrongConstant]\n"
+ " setDuration(8); // ERROR (not matching number range)\n"
+ " ~\n"
+ "3 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(14, 23),
java("src/test/pkg/X.java", ""
+ "\n"
+ "package test.pkg;\n"
+ "\n"
+ "import android.support.annotation.IntDef;\n"
+ "import android.support.annotation.IntRange;\n"
+ "\n"
+ "import java.lang.annotation.Retention;\n"
+ "import java.lang.annotation.RetentionPolicy;\n"
+ "\n"
+ "@SuppressWarnings({\"UnusedParameters\", \"unused\", \"SpellCheckingInspection\"})\n"
+ "public class X {\n"
+ "\n"
+ " public static final int UNRELATED = 500;\n"
+ "\n"
+ " @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})\n"
+ " @IntRange(from = 10)\n"
+ " @Retention(RetentionPolicy.SOURCE)\n"
+ " public @interface Duration {}\n"
+ "\n"
+ " public static final int LENGTH_INDEFINITE = -2;\n"
+ " public static final int LENGTH_SHORT = -1;\n"
+ " public static final int LENGTH_LONG = 0;\n"
+ " public void setDuration(@Duration int duration) {\n"
+ " }\n"
+ "\n"
+ " public void test() {\n"
+ " setDuration(UNRELATED); /// ERROR: Not right intdef, even if it's in the right number range\n"
+ " setDuration(-5); // ERROR (not right int def or value\n"
+ " setDuration(8); // ERROR (not matching number range)\n"
+ " setDuration(8000); // OK (@IntRange applies)\n"
+ " setDuration(LENGTH_INDEFINITE); // OK (@IntDef)\n"
+ " setDuration(LENGTH_LONG); // OK (@IntDef)\n"
+ " setDuration(LENGTH_SHORT); // OK (@IntDef)\n"
+ " }\n"
+ "}\n"),
copy("src/android/support/annotation/IntDef.java.txt", "src/android/support/annotation/IntDef.java"),
copy("src/android/support/annotation/IntRange.java.txt", "src/android/support/annotation/IntRange.java")
));
}
}