blob: b69ae3607820ebe197ad3749ebdd47516b4a4662 [file] [log] [blame]
/*
* Copyright (C) 2012 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 com.android.tools.lint.checks.MissingClassDetector.Companion.INNERCLASS
import com.android.tools.lint.checks.MissingClassDetector.Companion.INSTANTIATABLE
import com.android.tools.lint.checks.MissingClassDetector.Companion.MISSING
import com.android.tools.lint.detector.api.Detector
class MissingClassDetectorTest : AbstractCheckTest() {
override fun getDetector(): Detector {
return MissingClassDetector()
}
fun testMenu() {
lint()
.issues(INSTANTIATABLE)
.files(
xml(
"res/menu/my_menu.xml",
"""
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/locale_search_menu"
android:title="@string/locale_search_menu"
android:icon="@*android:drawable/ic_search_api_material"
android:showAsAction="always|collapseActionView"
android:actionViewClass="test.pkg.SearchView" />
<item
android:id="@+id/locale_search_menu"
android:title="@string/locale_search_menu"
android:icon="@*android:drawable/ic_search_api_material"
android:showAsAction="always|collapseActionView"
android:actionViewClass="test.pkg.NotView" />
</menu>
"""
).indented(),
kotlin(
"""
package test.pkg
import android.widget.TextView
abstract class SearchView : TextView(null)
"""
).indented(),
kotlin(
"""
package test.pkg
abstract class NotView : android.app.Fragment()
"""
).indented()
).run()
.expect(
"""
res/menu/my_menu.xml:13: Error: NotView must extend android.view.View [Instantiatable]
android:actionViewClass="test.pkg.NotView" />
~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
)
}
fun testTransition() {
lint()
.issues(INSTANTIATABLE)
.files(
xml(
"res/transition/my_transition.xml",
"""
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together">
<transition
class="test.pkg.MyTransition"
android:duration="250" />
<transition
class="test.pkg.NotTransition"
android:duration="250" />
<fade
android:duration="100"
android:fromAlpha="0.1"
android:toAlpha="1.0" />
</transitionSet>
"""
).indented(),
kotlin(
"""
package test.pkg
import android.app.Fragment
class MyClassicFragment : Fragment()
"""
).indented(),
kotlin(
"""
package test.pkg
abstract class NotTransition : android.app.Fragment()
"""
).indented()
).run()
.expect(
"""
res/transition/my_transition.xml:7: Error: NotTransition must extend android.transition.Transition [Instantiatable]
class="test.pkg.NotTransition"
~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
)
}
fun testDrawable() {
lint()
.issues(INSTANTIATABLE)
.files(
xml(
"res/drawable/my_drawable.xml",
"""
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<drawable
class="test.pkg.MyDrawable"/>
</background>
<foreground>
<drawable
class="test.pkg.NotDrawable"/>
</foreground>
</adaptive-icon>
"""
).indented(),
kotlin(
"""
package test.pkg
import android.graphics.drawable.Drawable
abstract class MyDrawable : Drawable(null)
"""
).indented(),
kotlin(
"""
package test.pkg
abstract class NotDrawable : android.app.Fragment()
"""
).indented()
).run()
.expect(
"""
res/drawable/my_drawable.xml:8: Error: NotDrawable must extend android.graphics.drawable.Drawable [Instantiatable]
class="test.pkg.NotDrawable"/>
~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
)
}
fun testCustomView() {
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
xml(
"res/layout/customview.xml",
"""
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/newlinear"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<foo.bar.Baz />
<test.pkg.MyView />
<test.pkg.NotView />
</LinearLayout>
"""
).indented(),
kotlin(
"""
package test.pkg
abstract class MyView : androic.view.View(null)
"""
).indented(),
kotlin(
"""
package test.pkg
abstract class NotView : android.app.Fragment()
"""
).indented()
)
.run()
.expect(
"""
res/layout/customview.xml:5: Error: Class referenced in the layout file, foo.bar.Baz, was not found in the project or the libraries [MissingClass]
<foo.bar.Baz />
~~~~~~~~~~~~~~~
res/layout/customview.xml:6: Error: MyView must extend android.view.View [Instantiatable]
<test.pkg.MyView />
~~~~~~~~~~~~~~~~~~~
res/layout/customview.xml:7: Error: NotView must extend android.view.View [Instantiatable]
<test.pkg.NotView />
~~~~~~~~~~~~~~~~~~~~
3 errors, 0 warnings
"""
)
}
fun testFragment() {
lint()
.issues(INSTANTIATABLE)
.files(
xml(
"res/layout/my_layout.xml",
"""
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<fragment class="test.pkg.MyClassicFragment" android:id="@+id/classic" />
<fragment class="test.pkg.MySupportFragment" android:id="@+id/support" />
<fragment class="test.pkg.MyAndroidXFragment" android:id="@+id/androidx" />
<fragment class="test.pkg.NotFragment" android:id="@+id/notfragment" />
</FrameLayout>
"""
).indented(),
kotlin(
"""
package test.pkg
import android.app.Fragment
class MyClassicFragment : Fragment()
"""
).indented(),
kotlin(
"""
package test.pkg
class MySupportFragment : android.support.v4.app.Fragment()
"""
).indented(),
kotlin(
"""
package test.pkg
import androidx.fragment.app.Fragment
class MyAndroidXFragment : Fragment()
"""
).indented(),
kotlin(
"""
package test.pkg
abstract class NotFragment : android.view.View(null)
"""
).indented(),
java(
"""
package android.support.v4.app;
public class Fragment { // support lib stub
}
"""
).indented(),
java(
"""
package androidx.fragment.app;
public class Fragment { // androidx stub
}
"""
).indented()
).run()
.expect(
"""
res/layout/my_layout.xml:6: Error: NotFragment must be a fragment [Instantiatable]
<fragment class="test.pkg.NotFragment" android:id="@+id/notfragment" />
~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
)
}
fun testManifestMissing() {
lint().issues(MISSING).files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.pkg"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity android:name=".TestProvider" />
<service android:name="test.pkg.TestProvider2" />
<provider android:name=".TestService" />
<receiver android:name="OnClickActivity" />
<service android:name="TestReceiver" />
</application>
</manifest>
"""
).indented()
).run().expect(
"""
AndroidManifest.xml:9: Error: Class referenced in the manifest, test.pkg.TestProvider, was not found in the project or the libraries [MissingClass]
<activity android:name=".TestProvider" />
~~~~~~~~~~~~~
AndroidManifest.xml:10: Error: Class referenced in the manifest, test.pkg.TestProvider2, was not found in the project or the libraries [MissingClass]
<service android:name="test.pkg.TestProvider2" />
~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:11: Error: Class referenced in the manifest, test.pkg.TestService, was not found in the project or the libraries [MissingClass]
<provider android:name=".TestService" />
~~~~~~~~~~~~
AndroidManifest.xml:12: Error: Class referenced in the manifest, test.pkg.OnClickActivity, was not found in the project or the libraries [MissingClass]
<receiver android:name="OnClickActivity" />
~~~~~~~~~~~~~~~
AndroidManifest.xml:13: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingClass]
<service android:name="TestReceiver" />
~~~~~~~~~~~~
5 errors, 0 warnings
"""
)
}
fun testManifestWrongType() {
lint().issues(MISSING, INSTANTIATABLE).files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.pkg"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<application
android:name=".TestProvider"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity android:name=".TestService" />
<service android:name="test.pkg.TestProvider2" />
<provider android:name=".TestReceiver" />
<receiver android:name="OnClickActivity" />
</application>
</manifest>
"""
).indented(),
onClickActivity,
testService,
testProvider,
testProvider2,
testReceiver
).run().expect(
"""
AndroidManifest.xml:7: Error: TestProvider must extend android.app.Application [Instantiatable]
android:name=".TestProvider"
~~~~~~~~~~~~~
AndroidManifest.xml:10: Error: TestService must extend android.app.Activity [Instantiatable]
<activity android:name=".TestService" />
~~~~~~~~~~~~
AndroidManifest.xml:11: Error: TestProvider2 must extend android.app.Service [Instantiatable]
<service android:name="test.pkg.TestProvider2" />
~~~~~~~~~~~~~~~~~~~~~~
AndroidManifest.xml:12: Error: TestReceiver must extend android.content.ContentProvider [Instantiatable]
<provider android:name=".TestReceiver" />
~~~~~~~~~~~~~
AndroidManifest.xml:13: Error: OnClickActivity must extend android.content.BroadcastReceiver [Instantiatable]
<receiver android:name="OnClickActivity" />
~~~~~~~~~~~~~~~
5 errors, 0 warnings
"""
)
}
fun testManifestOkWithSources() {
// Checks resolving with source code
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
androidManifestRegs,
classpath(),
onClickActivity,
testService,
testProvider,
testProvider2,
testReceiver
)
.run()
.expectClean()
}
fun testManifestOkWithBytecode() {
// Checks resolving with bytecode
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
androidManifestRegs,
classpath(),
base64gzip(
"libs/classes.jar",
"" +
"H4sIAAAAAAAAAJVWCTSUbRseBt+MyZdlsjRjzxChH5F9KbLNGFsllCVpwpRl" +
"aCxZJzPCZEu2JorEEGkxjRT5KJQt+5aMlAhFi6X5R8sXzuE///Oe95z3nPe+" +
"r/t+nvu67+dCWwA5oAAQ63mGQRgCVi0wgAOANLYzVDRDmSh/bwQAgAC0xV+g" +
"lV/sv0zQGzpDWe+/zkhDlJmJsa2dEtLkE7K5ydJCUamNx0JR/kVzS4XN7peq" +
"r8b8lMyRu8yQbbgiDvDldyJ1MJjCyb2CUMZ1+bNvZN5jPvrN+rH9iG4Q+OKL" +
"Dgtb51d07h/RMtZF52S9AR7+Acobm4B/m5zx8lT+s4/1ZmKrzayw+7wx7l6G" +
"7gGYQEwAXsnd29Xfn5A8iOU3hDYwPXNDFRRdbt6VvyuPTsZS7yHc1JACKYis" +
"YOvo9BMQmNbtSewlQiPs6+6hy5dPVs2whUKO1Z090evMl3uxOVtTnDmsqdrU" +
"MjwcDug5IA0mlI2IAK1wwc7zd3HecYHThVOkpJl6szqwzOBE5b23hQ9GC13P" +
"XzyrtGvkoUTdqXlByXPeQfFb3EzHy6I4JG+Bm/aKfm7h+ZJwDrs9JurEfVxF" +
"Pt1owXD3B2cim6uto9q+FEfnB/c6w6Binx3stZOQZjNa4FEyeCxTEKXNX5/f" +
"97rkXXp9vpXci2exC7Ix8/iF4JcjLtbGCtS9O+QCyRenyOFhS+0aVRD6kyGD" +
"OdGOs+MJPrLU0MxadSumJEG0dt7srQNB/KmaloiOwR5SbExnWW+rQEFR7yC8" +
"Cy1t59UXEna9uACL61Rt2Xbw/rfw/agrmhWx2P6zMgyv9BNiQVbmZTzf4y4+" +
"2kX5+n72ynErDwvvQ4GPpOYztnKrVMkuucRdiBy4HjG1515okunATNjRitrJ" +
"1ItSrmLFkTOPb1GkmxdZCajwNnZs5/5+1TiVvc7iaDmemEcutEy2JcLi80EF" +
"ZYru0hGit9MU03puiBzBn5KQydvJN4VQTx/EeNXkIIhqWGMiYdrh27huPrqv" +
"ZOxhUOkksvIh3No5qrIC3lbJzzaQwHvASbq4nWA+MqQxEL4olDYrELJ0w+7G" +
"mXeg9ERE0WLmoP4KW5NDozuFWc3Czrm6V9ZzDLaaY3asD7Tf6UDMcQ+/nwRL" +
"s3H2EjCE6moMmzn23ynoXGiz88HEVUpImV6XYzMEy6txWfMieiVRCXTfQPVW" +
"JiNaD6z3Fbhk/XqwFynXa/9erzknJ6Qlw78lh/Komgk4I7TowQDxSANHLfXi" +
"s8VOHz0VXuR0s0mONzBm/NQx2ovWL/Fz8R2kUSoRHVyo45EI+hZ9kIJ++7WU" +
"KkmeUImT4ztIb0xpeUIG3gxx230pZr5UHzpa3Sq/YPU35M1DbpDjoKWiT3VD" +
"mjum8mONYRCiMaUen29PD5EqqBWBnGbAYSVV1os+C4XzqXvOH+Gb2MlutMdl" +
"Ide6WghsGQlW+BTVEFxnrqFSKpF6oH0OJbvLX1mxIo0gt1P5Yh+vlkjYcs1y" +
"tXCsikt1gHj+zeezjO9sCLvQW9fyiTpjvQ8lofoEGwp/AUZG5PmVprn4/zTT" +
"nF3rDDja38VozVwT6Ub06Wcgcxs93xASxR32+pa0djN92mnDJlM9W+fRetZt" +
"/lRKWFhQXvBkVJjRXBq+4XRWtwpTE05yn52ByR/Dp+xL6E7WpbqQuscqI0a5" +
"D9RyUhGvPOEQPgQTz0Y7nhTrq25oQt/z0kcvFuc9s31KKa+mHZTVY3522v+O" +
"5sLtx/QTeA0BhpTyCL+SLJycfL4DztPUFl+R0XfPl3Co1t7Jogcrq56vPag3" +
"ao390Iab3PE0CG6RoZUpiId6quLUHLywHw6e+3BwckKJso32kQuLCJqfPFlh" +
"96Gf/uGf3kdz2TihkUvL/Ys8K8RrSbW5sptFPPymxINvRDyVn8zzQTX9XYPm" +
"1b3WadNiSiwRFczs4YeR7KX53cIdNZxuNdp/bzJ3GwGGpgqnJ9RZ+p14kY3X" +
"vXJfO0KpUckm5ogUmRTes2tZRFZAlNJpNFEKyUIvF/NlV/cVHzcoyyzRrBGr" +
"h7yrMVInq8GNdqJDNPWmjG56Sg2YG8kQpctzICVDS453UtK0+CdqmELBHKe3" +
"UwJ2HGic/Gqt7eUreDKC8rifmENWfdAc+iQyYDGumZAC1TFpgCoyhD9RuBI5" +
"VY+qOecLrRxFe9IEsIa1UXG2/6sHbTzcPTCBv3sQZ91k/oR1Eh2HvKldRSVd" +
"QoLQI0Jc4vS4KM5ctFMh1GkIIfSwD5F83ZhpfigqSvzVAVJKf0T+XYfmjO6M" +
"7IzF5U+znK94Hbn4Y9zkoluDg6sqsD205Ht2wHcwUz75WlyolealKVpuwL73" +
"A19i3C88Ujd6s58mryOzo/iuumTnDBwjRfL+G3Mc9O2S35jHo+6k7wlvA2Rc" +
"y8d68/oFq+Ne783gnrY3OIxZLi5APQ3GwvkSi0t8PRJAlCcL+3Nv5ophVCdd" +
"BXJ10logO3MkqagsY1Pt5aPThDLHEqQacqAQ60LQWAwqzwEKyKRecyzh6yqy" +
"7SK/zOo+Onx/jBD3wl5YcVqfGCnByCxVWDY5xBG5Bc2mD85Tfiamz+kYLX0h" +
"SbgjZPoy3l2WLcXHRJan+fZzHpjwuDD409MGhqpuov9onQNyQEzQ3Y2hePXq" +
"+6WJw24m7E1y9U8S76hILJLaFYsdwkRhmeLWKQmxiLLSKS51EWRp2tytGchK" +
"+YwY8NRAVum62Dcr3/b15bP18AvEuHv8qh65HMVuyHueGRndAeSM5HXULnMR" +
"gMDiCQjEDiEYaNnvfpv9gEzQXy2ENBJh66t9Ip7pHcLXHJv1K5ihGRlfyN0a" +
"AM+3hELeYEIjpDy4SkAVVSDf/3Y/AQRyxrQ3T9g97TRP5co9l5ugpATurDmD" +
"ApX6FEnZlQ/v58EZKXSZDJZSBOWznv2TTEJ5C9CymyzpD5a2xIXkCCzMHhtt" +
"0KnPVPCxqT3JGz6VdNhWzpZISp+d6S+jXa7EpmnahGSdWRI3JRYE5shMmNDt" +
"+/QRZDGGyWuOULGB4jP1nr5baPzUuiPaYTVDFfelE3FMkbyacpqXi66LDDzP" +
"vnacuq2h9bNQ78zx8PFL15v69n4EaldPnGNfOVh65eAEH+tgaT/6go0dClir" +
"5H5rvBUZuHatEYXrXVcLNOgaN90NJOEKAjdgYyH3Z9EB/8q6jV3Aa1yGAGtk" +
"3p9cV9xWX89ia9w42f6X7FuPtXriwtZgYYCbXu/rgVbPK/gaoNecm4/r9Uir" +
"W2dtSlNcm0679UCrqbJ9DRAVtFnfoS04uX6WBAw4xEqC80fF/gu8+KimcgwA" +
"AA=="
)
)
.run()
.expectClean()
}
fun testInnerClassStaticSource() {
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
androidManifest,
classpath(),
java(
"" +
"package test.pkg;\n" +
"\n" +
"import android.app.Activity;\n" +
"\n" +
"public class Foo {\n" +
" public static class Bar extends Activity {\n" +
" }\n" +
" public class Baz extends Activity {\n" +
" }\n" +
"}\n"
)
)
.run()
.expect(
"""
AndroidManifest.xml:21: Error: This inner class should be static (test.pkg.Foo$Baz) [Instantiatable]
android:name=".FooBaz"
~~~~~~~~
1 errors, 0 warnings
"""
)
}
fun testInnerClassStaticBytecode() {
lint()
.issues(INSTANTIATABLE)
.files(
androidManifest,
jar(
"libs/bytecode.jar",
base64gzip(
"test/pkg/Foo.class",
"" +
"H4sIAAAAAAAAAGVOu07DMBQ9t03jNoS2hNeMxAAMWOoK6kClSkgRDKDuTrCK" +
"S0iQ7TL0r5iQGPiAfhTiJnSohC2dex7X8ln/fH0DGOFYoEWIvXZevr3M5bSq" +
"BALCcKHelSxUOZf32ULnnhBem9L4MaF9dj4jBJPqSUdooxejg5AwSE2p75av" +
"mbaPKis0IUmrXBUzZU2tN2bgn40j9NPtP68I0UO1tLmemnqpy95lXYG73Zal" +
"tpNCOaedQMLdtl+e3ijLlRgFDv9nqyZb4QRMUB/iy30ZBSvZaKBz8YnuB5MW" +
"Isbwz8QOY7zhMXabvN/gAEOeCbM9zvfRwwHPI9Av5fj6R1sBAAA="
),
base64gzip(
"test/pkg/Foo\$Bar.class",
"" +
"H4sIAAAAAAAAAF1Oy0oDQRCsTja7ybqah/kBwYN6cMCrImggEAhelNwnu4OO" +
"rjPLzCTgZ3kSPPgBfpTYO3oQu6G6q7qL7s+v9w8AZ5hm6BBGQfkgmqd7Mbf2" +
"8Fq6DAlhKk3lrK6EbBpxVQa91eGFkF5oo8MloXt0vCIkM1upHF0MCvSQEoZL" +
"bdTN5nmt3J1c14owWdpS1ivpdMt/xSQ8aN/O/t8+J+S3duNKNdftYp/100e5" +
"lYRiYYxys1p6r3yGCSt/3fwS+3EAbtAGcfJPjBkzETnQO3lD/5WbDnLGNIop" +
"dhiLnwWuu3G+F3GIEdc82sfYx+AbAxaHhz8BAAA="
),
base64gzip(
"test/pkg/Foo\$Baz.class",
"" +
"H4sIAAAAAAAAAF1QTUvDQBB9k8SkjbFNa/06Cj1UBSPiTRG0UBCCF6X3bbLo" +
"akxCsi3ov/Igggd/gD9KnA0eqgw8Zt57M/vYr++PTwDH2PFgEUItax2Vj3fR" +
"pCiGl+LFg0MYiDytCpVGoiyji0SrhdLPBFffq3p4ROjEy2unrJypXOlzQm/0" +
"V9qbEpxxkco2CKsBVuD6sLEWwEOHYI+MoRurXF7Pn2ayuhWzTBL6cZGIbCoq" +
"ZeZf0jHPG+1/Zg7g3xTzKpETZYwt5g8fxEIQgqs8l9U4E3Utaw9bzCxvcwLe" +
"xy6Hs/lXKAxNQtNxeWgxtnk6gcUF+PsHbwzvCF55stBldNkDdoaMQdP76KHf" +
"6OsNDrDRsObmJrZBP1YpAveEAQAA"
)
)
)
.run()
.expect(
"""
AndroidManifest.xml:21: Error: This inner class should be static (test.pkg.Foo$Baz) [Instantiatable]
android:name=".FooBaz"
~~~~~~~~
1 errors, 0 warnings
"""
)
}
fun testInnerClassPublicSource() {
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.pkg.Foo" >
<application>
<activity
android:name=".Bar"
android:label="@string/app_name" >
</activity>
</application>
</manifest>
"""
).indented(),
java(
"" +
"package test.pkg.Foo;\n" +
"\n" +
"import android.app.Activity;\n" +
"\n" +
"public class Bar extends Activity {\n" +
" private Bar() {\n" +
" }\n" +
"}\n"
)
)
.run()
.expect(
"""
AndroidManifest.xml:5: Error: The default constructor must be public in test.pkg.Foo.Bar [Instantiatable]
android:name=".Bar"
~~~~
1 errors, 0 warnings
"""
)
}
fun testInnerClassPublicBytecode() {
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.pkg.Foo" >
<application>
<activity
android:name=".Bar"
android:label="@string/app_name" >
</activity>
</application>
</manifest>
"""
).indented(),
classpath(),
jar(
"libs/foo.jar",
base64gzip(
"test/pkg/Foo/Bar.class",
"" +
"H4sIAAAAAAAAAF2NzU7CQBRGv1sKxVopIb6AO3XBJLrUmIgJK+JGwn7aTvQq" +
"dJrpQOJjuTJxwQPwUIY76Iq7mHN/Tubb/f5sAdxgmCAiDL1pvWo+XtXUWjXR" +
"LkFMONd15SxXSjeNeiw9b9h/Enr3XLN/IHQurxaE+MlWJkUHJxm66BHyGdfm" +
"eb0qjJvrYmkIo5kt9XKhHYf5fxn7N27D7Tj7jpC+2LUrzZSD2Jfd+F1vNC4g" +
"oQhFiEKYMJHpVkjC7vU3+l/SREjlTYUQKRbpVLrsTxKeHb4YHMx8D9v6x2YM" +
"AQAA"
)
)
)
.run()
.expect(
"""
AndroidManifest.xml:5: Error: The default constructor must be public in test.pkg.Foo.Bar [Instantiatable]
android:name=".Bar"
~~~~
1 errors, 0 warnings
"""
)
}
fun testInnerClassFix() {
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.pkg" >
<application>
<activity
android:name=".Foo.Bar"
android:label="@string/app_name" >
</activity>
</application>
</manifest>
"""
).indented(),
java(
"" +
"package test.pkg;\n" +
"\n" +
"import android.app.Activity;\n" +
"\n" +
"public class Foo {\n" +
" public static class Bar extends Activity {\n" +
" }\n" +
"}\n"
)
)
.run()
.expect(
"""
AndroidManifest.xml:5: Warning: Use '$' instead of '.' for inner classes; replace ".Foo.Bar" with ".Foo$Bar" [InnerclassSeparator]
android:name=".Foo.Bar"
~~~~~~~~
0 errors, 1 warnings
"""
)
.expectFixDiffs(
"""
Fix for AndroidManifest.xml line 5: Replace with .FooBar:
@@ -5 +5
- android:name=".Foo.Bar"
+ android:name=".Foo$Bar"
"""
)
}
fun testAnalytics() {
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
xml(
"res/values/analytics.xml",
"""
<resources>
<!--Replace placeholder ID with your tracking ID-->
<string name="ga_trackingId">UA-12345678-1</string>
<!--Enable Activity tracking-->
<bool name="ga_autoActivityTracking">true</bool>
<!--Enable automatic exception tracking-->
<bool name="ga_reportUncaughtExceptions">true</bool>
<!-- The screen names that will appear in your reporting -->
<string name="com.example.app.BaseActivity">Home</string>
<string name="com.example.app.PrefsActivity">Preferences</string>
<string name="test.pkg.OnClickActivity">Clicks</string>
</resources>
"""
).indented(),
onClickActivity
)
.run()
.expect(
"""
res/values/analytics.xml:12: Error: Class referenced in the analytics file, com.example.app.BaseActivity, was not found in the project or the libraries [MissingClass]
<string name="com.example.app.BaseActivity">Home</string>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
res/values/analytics.xml:13: Error: Class referenced in the analytics file, com.example.app.PrefsActivity, was not found in the project or the libraries [MissingClass]
<string name="com.example.app.PrefsActivity">Preferences</string>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors, 0 warnings
"""
)
}
fun testMissingClass() {
// Regression test for b/36958791
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
xml(
"res/layout/user_prefs_fragment.xml",
"""
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
class="course.examples.DataManagement.PreferenceActivity.ViewAndUpdatePreferencesActivity$UserPreferenceFragment"
android:id="@+id/userPreferenceFragment">
</fragment>
"""
).indented(),
kotlin(
"""
package course.examples.DataManagement.PreferenceActivity
import android.app.Fragment
class ViewAndUpdatePreferencesActivity {
class UserPreferenceFragment : Fragment() {
}
}
"""
)
)
.run()
.expectClean()
}
fun testWrongType() {
lint()
.issues(MISSING, INSTANTIATABLE, INNERCLASS)
.files(
xml(
"res/layout/user_prefs_fragment.xml",
"""
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
class="course.examples.dataManagement.ViewAndUpdatePreferencesActivity"
android:id="@+id/userPreferenceFragment">
</fragment>
"""
).indented(),
java(
"src/course/examples/dataManagement/ViewAndUpdatePreferencesActivity.java",
"""
package course.examples.DataManagement;
public class ViewAndUpdatePreferencesActivity {
}
"""
).indented()
)
.run()
.expect(
"""
res/layout/user_prefs_fragment.xml:2: Error: ViewAndUpdatePreferencesActivity must be a fragment [Instantiatable]
class="course.examples.dataManagement.ViewAndUpdatePreferencesActivity"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
)
}
fun testHeaders() {
// See https://code.google.com/p/android/issues/detail?id=51851
lint()
.issues(MISSING, INNERCLASS)
.files(
xml(
"res/xml/prefs_headers.xml",
"""
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="foo.bar.MyFragment$Missing"
android:summary="@string/summary"
android:title="@string/title" />
<header android:fragment="test.pkg.FragmentTest$Fragment1" />
<header android:fragment="test.pkg.FragmentTest.Fragment1" />
</preference-headers>
"""
).indented(),
java(
"""
package test.pkg;
import android.app.Fragment;
public class FragmentTest {
private static class Fragment1 extends Fragment {
}
}
"""
).indented()
)
.run()
.expect(
"""
res/xml/prefs_headers.xml:3: Error: Class referenced in the preference header file, foo.bar.MyFragmentMissing, was not found in the project or the libraries [MissingClass]
android:fragment="foo.bar.MyFragment$Missing"
~~~~~~~~~~~~~~~~~~~~~~~~~~
res/xml/prefs_headers.xml:7: Warning: Use '$' instead of '.' for inner classes; replace "test.pkg.FragmentTest.Fragment1" with "test.pkg.FragmentTest$Fragment1" [InnerclassSeparator]
<header android:fragment="test.pkg.FragmentTest.Fragment1" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 1 warnings
"""
)
.expectFixDiffs(
"""
Fix for res/xml/prefs_headers.xml line 7: Replace with test.pkg.FragmentTestFragment1:
@@ -7 +7
- <header android:fragment="test.pkg.FragmentTest.Fragment1" />
+ <header android:fragment="test.pkg.FragmentTest$Fragment1" />
"""
)
}
private val androidManifest = manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.pkg"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="16" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".Foo$Bar"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Foo$Baz"
android:label="@string/app_name" >
</activity>
</application>
</manifest>
"""
).indented()
private val androidManifestRegs = manifest(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.pkg"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<provider android:name=".TestProvider" />
<provider android:name="test.pkg.TestProvider2" />
<service android:name=".TestService" />
<activity android:name="OnClickActivity" />
<receiver android:name="TestReceiver" />
</application>
</manifest>
"""
).indented()
private val testProvider = java(
"""
package test.pkg;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class TestProvider extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
}
"""
).indented()
private val testProvider2 = java(
"""
package test.pkg;
public class TestProvider2 extends TestProvider {
}
"""
).indented()
private val testReceiver = java(
"""
package test.pkg;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class TestReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
// Anonymous classes should NOT be counted as a must-register
private BroadcastReceiver dummy() {
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
}
};
}
}
"""
).indented()
private val onClickActivity = java(
"""
package test.pkg;
import android.app.Activity;
public class OnClickActivity extends Activity {
}
"""
).indented()
private val testService = java(
"""
package test.pkg;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class TestService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
"""
).indented()
}