blob: 6d26e65fa121433c616ed28a67c64053e84aa409 [file] [log] [blame]
/*
* Copyright (C) 2014 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.AnnotationDetectorTest.SUPPORT_ANNOTATIONS_JAR_BASE64_GZIP;
import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Detector;
import java.io.IOException;
import java.io.StringReader;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
public class LayoutInflationDetectorTest extends AbstractCheckTest {
@Override
protected Detector getDetector() {
return new LayoutInflationDetector();
}
@SuppressWarnings("all")
private TestFile mLayoutInflationTest =
java(
"src/test/pkg/LayoutInflationTest.java",
""
+ "package test.pkg;\n"
+ "\n"
+ "import android.content.Context;\n"
+ "import android.view.LayoutInflater;\n"
+ "import android.view.View;\n"
+ "import android.view.ViewGroup;\n"
+ "import android.widget.BaseAdapter;\n"
+ "import android.annotation.SuppressLint;\n"
+ "import java.util.ArrayList;\n"
+ "\n"
+ "public abstract class LayoutInflationTest extends BaseAdapter {\n"
+ " public View getView(int position, View convertView, ViewGroup parent) {\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, null);\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, null, true);\n"
+ " //convertView = mInflater.inflate(R.layout.your_layout);\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, parent);\n"
+ " convertView = WeirdInflater.inflate(convertView, null);\n"
+ "\n"
+ " return convertView;\n"
+ " }\n"
+ "\n"
// Suppressed checks
+ " @SuppressLint(\"InflateParams\")\n"
+ " public View getView2(int position, View convertView, ViewGroup parent) {\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, null);\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, null, true);\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, parent);\n"
+ " convertView = WeirdInflater.inflate(convertView, null);\n"
+ "\n"
+ " return convertView;\n"
+ " }\n"
// Test/Stub Setup
+ " private LayoutInflater mInflater;\n"
+ " private static class R {\n"
+ " private static class layout {\n"
+ " public static final int your_layout = 1;\n"
+ " }\n"
+ " }\n"
+ " private static class WeirdInflater {\n"
+ " public static View inflate(View view, Object params) { return null; }\n"
+ " }\n"
+ "}\n");
@Override
protected boolean allowCompilationErrors() {
// Some of these unit tests are still relying on source code that references
// unresolved symbols etc.
return false;
}
private static XmlPullParser createXmlPullParser(@NonNull String xml)
throws XmlPullParserException {
// Instantiate an XML pull parser based on the contents of the file.
XmlPullParser parser;
parser = new KXmlParser(); // Parser for regular text XML.
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(new StringReader(xml));
return parser;
}
public void testFull() {
String expected =
""
+ "src/test/pkg/LayoutInflationTest.java:13: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, null);\n"
+ " ~~~~\n"
+ "src/test/pkg/LayoutInflationTest.java:14: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, null, true);\n"
+ " ~~~~\n"
+ "0 errors, 2 warnings\n";
lint().files(
mLayoutInflationTest,
xml(
"res/layout/your_layout.xml",
"<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ " android:id=\"@+id/LinearLayout1\"\n"
+ " android:layout_width=\"match_parent\"\n"
+ " android:layout_height=\"match_parent\"\n"
+ " android:orientation=\"vertical\" />\n"),
xml(
"res/layout-port/your_layout.xml",
""
+ "<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " android:id=\"@id/text1\"\n"
+ " style=\"?android:attr/listSeparatorTextViewStyle\" />\n"))
.run()
.expect(expected);
}
public void testIncremental() {
String expected =
""
+ "src/test/pkg/LayoutInflationTest.java:13: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, null);\n"
+ " ~~~~\n"
+ "src/test/pkg/LayoutInflationTest.java:14: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
+ " convertView = mInflater.inflate(R.layout.your_layout, null, true);\n"
+ " ~~~~\n"
+ "0 errors, 2 warnings\n";
lint().files(
mLayoutInflationTest,
xml(
"res/layout/your_layout.xml",
"<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ " android:id=\"@+id/LinearLayout1\"\n"
+ " android:layout_width=\"match_parent\"\n"
+ " android:layout_height=\"match_parent\"\n"
+ " android:orientation=\"vertical\" />\n"),
xml(
"res/layout-port/your_layout.xml",
""
+ "<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " android:id=\"@id/text1\"\n"
+ " style=\"?android:attr/listSeparatorTextViewStyle\" />\n"))
.incremental(mLayoutInflationTest.targetRelativePath)
.run()
.expect(expected);
}
public void testNoLayoutParams() {
lint().files(
mLayoutInflationTest,
xml(
"res/layout/your_layout.xml",
""
+ "<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " android:id=\"@id/text1\"\n"
+ " style=\"?android:attr/listSeparatorTextViewStyle\" />\n"))
.run()
.expectClean();
}
public void testHasLayoutParams() throws IOException, XmlPullParserException {
assertFalse(LayoutInflationDetector.hasLayoutParams(createXmlPullParser("")));
assertFalse(
LayoutInflationDetector.hasLayoutParams(createXmlPullParser("<LinearLayout/>")));
assertFalse(
LayoutInflationDetector.hasLayoutParams(
createXmlPullParser(
""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " android:orientation=\"vertical\" >\n"
+ "\n"
+ " <include\n"
+ " android:layout_width=\"wrap_content\"\n"
+ " android:layout_height=\"wrap_content\"\n"
+ " layout=\"@layout/layoutcycle1\" />\n"
+ "\n"
+ "</LinearLayout>")));
assertTrue(
LayoutInflationDetector.hasLayoutParams(
createXmlPullParser(
""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " android:layout_width=\"match_parent\"\n"
+ " android:layout_height=\"match_parent\"\n"
+ " android:orientation=\"vertical\" >\n"
+ "\n"
+ " <include\n"
+ " android:layout_width=\"wrap_content\"\n"
+ " android:layout_height=\"wrap_content\"\n"
+ " layout=\"@layout/layoutcycle1\" />\n"
+ "\n"
+ "</LinearLayout>")));
}
public void testAlertDialog() {
lint().files(
java(
""
+ "package test.pkg;\n"
+ "\n"
+ "import android.app.Activity;\n"
+ "import android.app.AlertDialog;\n"
+ "import android.app.Dialog;\n"
+ "import android.app.DialogFragment;\n"
+ "import android.os.Bundle;\n"
+ "import android.view.LayoutInflater;\n"
+ "import android.view.View;\n"
+ "import android.support.annotation.NonNull;\n"
+ "\n"
+ "public class AlertDialogTestJava {\n"
+ " public static class MyFragment extends DialogFragment {\n"
+ " @NonNull\n"
+ " @Override\n"
+ " public Dialog onCreateDialog(Bundle savedInstanceState) {\n"
+ " Activity activity = getActivity();\n"
+ " if (activity != null) {\n"
+ " AlertDialog.Builder alertBuilder = new AlertDialog.Builder(activity);\n"
+ " alertBuilder.setCustomTitle(activity.getLayoutInflater().inflate(R.layout.title, null));\n"
+ " View view = LayoutInflater.from(activity).inflate(R.layout.the_layout, null);\n"
+ " alertBuilder.setView(view);\n"
+ " return alertBuilder.create();\n"
+ " }\n"
+ " return super.onCreateDialog(savedInstanceState);\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " \n"
+ " public AlertDialog test2(Activity activity) {\n"
+ " AlertDialog.Builder builder = new AlertDialog.Builder(activity);\n"
+ " LayoutInflater inflater = activity.getLayoutInflater();\n"
+ " View rootView = inflater.inflate(R.layout.the_layout, null, false);\n"
+ " builder.setView(rootView);\n"
+ " builder.setTitle(\"Alert\");\n"
+ " return builder.create();\n"
+ " }\n"
+ "}"),
kotlin(
""
+ "package test.pkg\n"
+ "\n"
+ "import android.app.Activity\n"
+ "import android.app.AlertDialog\n"
+ "import android.app.Dialog\n"
+ "import android.app.DialogFragment\n"
+ "import android.os.Bundle\n"
+ "import android.view.LayoutInflater\n"
+ "class AlertDialogTestKotlin {\n"
+ " class MyFragment : DialogFragment() {\n"
+ " override fun onCreateDialog(savedInstanceState: Bundle): Dialog {\n"
+ " val activity = activity\n"
+ " if (activity != null) {\n"
+ " val alertBuilder =\n"
+ " AlertDialog.Builder(activity)\n"
+ " alertBuilder.setCustomTitle(activity.layoutInflater.inflate(R.layout.title, null))\n"
+ " val view = LayoutInflater.from(activity).inflate(R.layout.the_layout, null)\n"
+ " alertBuilder.setView(view)\n"
+ " return alertBuilder.create()\n"
+ " }\n"
+ " return super.onCreateDialog(savedInstanceState)\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " fun test2(activity: Activity): Dialog {\n"
+ " val builder = AlertDialog.Builder(activity)\n"
+ " val inflater = activity.layoutInflater\n"
+ " val rootView = inflater.inflate(R.layout.the_laoyut, null, false)\n"
+ " builder.setView(rootView)\n"
+ " builder.setTitle(\"Alert\")\n"
+ " return builder.create()\n"
+ " }\n"
+ "}"),
java(
""
+ "package test.pkg;\n"
+ "\n"
+ "import android.app.Activity;\n"
+ "import android.app.Dialog;\n"
+ "import android.os.Bundle;\n"
+ "import android.view.LayoutInflater;\n"
+ "import android.view.View;\n"
+ "import android.support.annotation.NonNull;\n"
+ "import androidx.appcompat.app.AlertDialog;\n"
+ "import androidx.fragment.app.DialogFragment;\n"
+ "\n"
+ "public class AlertDialogTestAndroidX {\n"
+ " public static class MyFragment extends DialogFragment {\n"
+ " @NonNull\n"
+ " @Override\n"
+ " public Dialog onCreateDialog(Bundle savedInstanceState) {\n"
+ " Activity activity = getActivity();\n"
+ " if (activity != null) {\n"
+ " AlertDialog.Builder alertBuilder = new AlertDialog.Builder(activity);\n"
+ " alertBuilder.setCustomTitle(activity.getLayoutInflater().inflate(R.layout.title, null));\n"
+ " View view = LayoutInflater.from(activity).inflate(R.layout.the_layout, null);\n"
+ " alertBuilder.setView(view);\n"
+ " return alertBuilder.create();\n"
+ " }\n"
+ " return super.onCreateDialog(savedInstanceState);\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " \n"
+ " public AlertDialog test2(Activity activity) {\n"
+ " AlertDialog.Builder builder = new AlertDialog.Builder(activity);\n"
+ " LayoutInflater inflater = activity.getLayoutInflater();\n"
+ " View rootView = inflater.inflate(R.layout.the_layout, null, false);\n"
+ " builder.setView(rootView);\n"
+ " builder.setTitle(\"Alert\");\n"
+ " return builder.create();\n"
+ " }\n"
+ "}"),
java(
// AndroidX Stub
""
+ "package androidx.appcompat.app;\n"
+ "\n"
+ "import android.app.Dialog;\n"
+ "import android.content.Context;\n"
+ "import android.content.DialogInterface;\n"
+ "import android.view.View;\n"
+ "\n"
+ "public class AlertDialog extends Dialog implements DialogInterface {\n"
+ " public AlertDialog(Context context) {\n"
+ " super(context);\n"
+ " }\n"
+ " public static class Builder {\n"
+ " public Builder setCustomTitle(View customTitleView) {\n"
+ " return this;\n"
+ " }\n"
+ "\n"
+ " public Builder setView(int layoutResId) {\n"
+ " return this;\n"
+ " }\n"
+ "\n"
+ " public Builder setView(View view) {\n"
+ " return this;\n"
+ " }\n"
+ " }\n"
+ "}\n"),
// Regression test for http://b/144314412
kotlin(
""
+ "package test.pkg\n"
+ "\n"
+ "import android.app.AlertDialog\n"
+ "import android.app.Dialog\n"
+ "import android.content.Context\n"
+ "import android.view.LayoutInflater\n"
+ "import android.view.View\n"
+ "\n"
+ "class AlertTest2(\n"
+ " private val alertDialogBuilderFactory: AlertDialogBuilderFactory\n"
+ ") {\n"
+ " fun createDialog(context: Context): Dialog {\n"
+ " val view =\n"
+ " LayoutInflater.from(context)\n"
+ " .inflate(R.layout.the_layout, /* root= */ null)\n"
+ "\n"
+ " return alertDialogBuilderFactory.create()\n"
+ " .setView(view)\n"
+ " .create()\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "class AlertDialogBuilderFactory {\n"
+ " // Usually named something with AlertBuilder but\n"
+ " // here we're not to test the return-type checks\n"
+ " fun create(): MyBuilder {\n"
+ " return MyBuilder()\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "class MyBuilder {\n"
+ " fun create(): AlertDialog {\n"
+ " return AlertDialog(null)\n"
+ " }\n"
+ "\n"
+ " fun setView(view: View?): MyBuilder {\n"
+ " return this\n"
+ " }\n"
+ "}\n"),
java(
// AndroidX Stub
""
+ "package androidx.fragment.app;\n"
+ "\n"
+ "public class DialogFragment extends android.app.Fragment {\n"
+ "}\n"),
xml(
"res/layout/the_layout.xml",
"<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ " android:id=\"@+id/LinearLayout1\"\n"
+ " android:layout_width=\"match_parent\"\n"
+ " android:layout_height=\"match_parent\"\n"
+ " android:orientation=\"vertical\" />\n"),
java(
""
+ ""
+ "package test.pkg;\n"
+ "public class R {\n"
+ " public static class layout {\n"
+ " public static final int the_layout=0x7f050000;\n"
+ " public static final int title=0x7f050001;\n"
+ " }\n"
+ "}"),
base64gzip(SUPPORT_JAR_PATH, SUPPORT_ANNOTATIONS_JAR_BASE64_GZIP))
.run()
.expectClean();
}
}