| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tools.lint.checks |
| |
| import com.android.SdkConstants.ABSOLUTE_LAYOUT |
| import com.android.SdkConstants.ANDROID_URI |
| import com.android.SdkConstants.ATTR_AUTO_TEXT |
| import com.android.SdkConstants.ATTR_CAPITALIZE |
| import com.android.SdkConstants.ATTR_EDITABLE |
| import com.android.SdkConstants.ATTR_INPUT_METHOD |
| import com.android.SdkConstants.ATTR_NUMERIC |
| import com.android.SdkConstants.ATTR_PASSWORD |
| import com.android.SdkConstants.ATTR_PERMISSION |
| import com.android.SdkConstants.ATTR_PHONE_NUMBER |
| import com.android.SdkConstants.ATTR_SINGLE_LINE |
| import com.android.SdkConstants.CLASS_PREFERENCE |
| import com.android.SdkConstants.EDIT_TEXT |
| import com.android.SdkConstants.TAG_SERVICE |
| import com.android.SdkConstants.TAG_USES_PERMISSION_SDK_23 |
| import com.android.SdkConstants.TAG_USES_PERMISSION_SDK_M |
| import com.android.SdkConstants.VALUE_FALSE |
| import com.android.SdkConstants.VALUE_TRUE |
| import com.android.resources.ResourceFolderType |
| import com.android.resources.ResourceFolderType.LAYOUT |
| import com.android.resources.ResourceFolderType.XML |
| import com.android.tools.lint.detector.api.Category |
| import com.android.tools.lint.detector.api.Implementation |
| import com.android.tools.lint.detector.api.Incident |
| import com.android.tools.lint.detector.api.Issue.Companion.create |
| import com.android.tools.lint.detector.api.JavaContext |
| import com.android.tools.lint.detector.api.ResourceXmlDetector |
| import com.android.tools.lint.detector.api.Scope |
| import com.android.tools.lint.detector.api.Scope.Companion.JAVA_FILE_SCOPE |
| import com.android.tools.lint.detector.api.Scope.Companion.MANIFEST_SCOPE |
| import com.android.tools.lint.detector.api.Scope.Companion.RESOURCE_FILE_SCOPE |
| import com.android.tools.lint.detector.api.Severity |
| import com.android.tools.lint.detector.api.SourceCodeScanner |
| import com.android.tools.lint.detector.api.XmlContext |
| import com.android.tools.lint.detector.api.minSdkAtLeast |
| import com.intellij.psi.PsiMethod |
| import org.jetbrains.uast.UCallExpression |
| import org.jetbrains.uast.UClass |
| import org.w3c.dom.Attr |
| import org.w3c.dom.Document |
| import org.w3c.dom.Element |
| import java.util.EnumSet |
| |
| /** Check which looks for usage of deprecated tags, attributes, etc. */ |
| class DeprecationDetector : ResourceXmlDetector(), SourceCodeScanner { |
| |
| // XML (resource and manifest) checks: |
| |
| override fun appliesTo(folderType: ResourceFolderType): Boolean { |
| return folderType == LAYOUT || folderType == XML |
| } |
| |
| override fun visitDocument(context: XmlContext, document: Document) { |
| if (context.resourceFolderType == XML) { |
| val rootElement = document.documentElement |
| val tagName = rootElement.tagName |
| if (tagName.startsWith("android.preference.")) { |
| context.report( |
| ISSUE, |
| rootElement, |
| context.getNameLocation(rootElement), |
| "The `android.preference` library is deprecated, it is " + |
| "recommended that you migrate to the AndroidX Preference " + |
| "library instead." |
| ) |
| return |
| } |
| if (tagName.startsWith("androidx.preference.")) { |
| // Qualified androidx preference tags can skip inheritance checking. |
| return |
| } |
| val parser = context.client.getUastParser(context.project) |
| val tagClass = parser.evaluator.findClass(rootElement.tagName) ?: return |
| if (parser.evaluator.inheritsFrom(tagClass, CLASS_PREFERENCE, false)) { |
| context.report( |
| ISSUE, |
| rootElement, |
| context.getNameLocation(rootElement), |
| "`$tagName` inherits from `android.preference.Preference` which is " + |
| "now deprecated, it is recommended that you migrate to the " + |
| "AndroidX Preference library." |
| ) |
| } |
| } |
| } |
| |
| override fun getApplicableElements(): Collection<String> { |
| return listOf(ABSOLUTE_LAYOUT, TAG_USES_PERMISSION_SDK_M) |
| } |
| |
| override fun getApplicableAttributes(): Collection<String>? { |
| return listOf( |
| // TODO: fill_parent is deprecated as of API 8. |
| // We could warn about it, but it will probably be very noisy |
| // and make people disable the deprecation check; let's focus on |
| // some older flags for now |
| // "fill_parent", |
| ATTR_EDITABLE, |
| ATTR_INPUT_METHOD, |
| ATTR_AUTO_TEXT, |
| ATTR_CAPITALIZE, |
| ATTR_NUMERIC, |
| ATTR_PHONE_NUMBER, |
| ATTR_PASSWORD, |
| ATTR_PERMISSION |
| // ATTR_SINGLE_LINE is marked deprecated, but (a) it's used a lot everywhere, |
| // including in our own apps, and (b) replacing it with the suggested replacement |
| // can lead to crashes; see issue 37137344 |
| // ATTR_ENABLED is marked deprecated in android.R.attr but apparently |
| // using the suggested replacement of state_enabled doesn't work, see issue b/36943030 |
| // These attributes are also deprecated; not yet enabled until we |
| // know the API level to apply the deprecation for: |
| // "ignored as of ICS (but deprecated earlier)" |
| // "fadingEdge", |
| // "This attribute is not used by the Android operating system." |
| // "restoreNeedsApplication", |
| // "This will create a non-standard UI appearance, because the search bar UI is |
| // changing to use only icons for its buttons." |
| // "searchButtonText", |
| ) |
| } |
| |
| override fun visitElement(context: XmlContext, element: Element) { |
| val tagName = element.tagName |
| var message = "`$tagName` is deprecated" |
| if (TAG_USES_PERMISSION_SDK_M == tagName) { |
| message += ": Use `$TAG_USES_PERMISSION_SDK_23 instead" |
| } |
| context.report(ISSUE, element, context.getNameLocation(element), message) |
| } |
| |
| override fun visitAttribute(context: XmlContext, attribute: Attr) { |
| if (ANDROID_URI != attribute.namespaceURI) { |
| return |
| } |
| val name = attribute.localName |
| val fix: String |
| var minSdk = 1 |
| when (name) { |
| ATTR_PERMISSION -> { |
| if (TAG_SERVICE == attribute.ownerElement.tagName && |
| CHOOSER_TARGET_SERVICE_PERM == attribute.value |
| ) { |
| context.report( |
| ISSUE, |
| attribute, |
| context.getLocation(attribute), |
| "ChooserTargetService` is deprecated: Please see $SHARE_API_URL", |
| fix().url(SHARE_API_URL).build() |
| ) |
| } |
| return |
| } |
| ATTR_EDITABLE -> { |
| fix = if (EDIT_TEXT != attribute.ownerElement.tagName) { |
| "Use an `<EditText>` to make it editable" |
| } else { |
| if (VALUE_TRUE == attribute.value) { |
| "`<EditText>` is already editable" |
| } else { |
| "Use `inputType` instead" |
| } |
| } |
| } |
| ATTR_SINGLE_LINE -> { |
| fix = if (VALUE_FALSE == attribute.value) { |
| "False is the default, so just remove the attribute" |
| } else { |
| "Use `maxLines=\"1\"` instead" |
| } |
| } |
| else -> { |
| fix = "Use `inputType` instead" |
| // The inputType attribute was introduced in API 3 so don't warn about |
| // deprecation if targeting older platforms |
| minSdk = 3 |
| } |
| } |
| val incident = Incident( |
| ISSUE, |
| attribute, |
| context.getLocation(attribute), |
| "`${attribute.name}` is deprecated: $fix" |
| ) |
| context.report(incident, minSdkAtLeast(minSdk)) |
| } |
| |
| // Kotlin and Java deprecation checks: |
| |
| override fun getApplicableConstructorTypes(): List<String> { |
| return listOf(FIREBASE_JOB_DISPATCHER_CLASS) |
| } |
| |
| override fun visitConstructor( |
| context: JavaContext, |
| node: UCallExpression, |
| constructor: PsiMethod |
| ) { |
| val url = "https://developer.android.com/topic/libraries/architecture/workmanager/migrating-fb" |
| context.report( |
| ISSUE, |
| node, |
| context.getCallLocation(node, includeReceiver = false, includeArguments = false), |
| "Job scheduling with `FirebaseJobDispatcher` is deprecated: Use AndroidX `WorkManager` instead", |
| fix().url(url).build() |
| ) |
| } |
| |
| override fun getApplicableMethodNames(): List<String> { |
| return listOf("getInstance") |
| } |
| |
| override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { |
| if (!context.evaluator.isMemberInClass(method, GCM_NETWORK_MANAGER_CLASS)) { |
| return |
| } |
| val url = "https://developer.android.com/topic/libraries/architecture/workmanager/migrating-gcm" |
| context.report( |
| ISSUE, |
| node, |
| context.getCallLocation(node, includeReceiver = false, includeArguments = false), |
| "Job scheduling with `GcmNetworkManager` is deprecated: Use AndroidX `WorkManager` instead", |
| fix().url(url).build() |
| ) |
| } |
| |
| override fun applicableSuperClasses(): List<String> { |
| return listOf(CHOOSER_TARGET_SERVICE_CLASS) |
| } |
| |
| override fun visitClass(context: JavaContext, declaration: UClass) { |
| val location = context.getNameLocation(declaration) |
| context.report( |
| ISSUE, |
| declaration, |
| location, |
| "`${declaration.name}` extends the deprecated `ChooserTargetService`: Use the Share API instead", |
| fix().url(SHARE_API_URL).build() |
| ) |
| } |
| |
| companion object { |
| @Suppress("SpellCheckingInspection") |
| private const val FIREBASE_JOB_DISPATCHER_CLASS = |
| "com.firebase.jobdispatcher.FirebaseJobDispatcher" |
| |
| private const val GCM_NETWORK_MANAGER_CLASS = |
| "com.google.android.gms.gcm.GcmNetworkManager" |
| |
| private const val CHOOSER_TARGET_SERVICE_CLASS = |
| "android.service.chooser.ChooserTargetService" |
| |
| private const val CHOOSER_TARGET_SERVICE_PERM = |
| "android.permission.BIND_CHOOSER_TARGET_SERVICE" |
| |
| private const val SHARE_API_URL = |
| "https://developer.android.com/training/sharing/receive.html?source=studio#providing-direct-share-targets" |
| |
| /** Usage of deprecated views or attributes. */ |
| @JvmField |
| val ISSUE = create( |
| id = "Deprecated", |
| briefDescription = "Using deprecated resources", |
| explanation = """ |
| Deprecated views, attributes and so on are deprecated because there \ |
| is a better way to do something. Do it that new way. You've been warned. |
| """, |
| category = Category.CORRECTNESS, |
| priority = 2, |
| severity = Severity.WARNING, |
| implementation = Implementation( |
| DeprecationDetector::class.java, |
| EnumSet.of(Scope.MANIFEST, Scope.RESOURCE_FILE, Scope.JAVA_FILE), |
| MANIFEST_SCOPE, |
| RESOURCE_FILE_SCOPE, |
| JAVA_FILE_SCOPE |
| ) |
| ) |
| } |
| } |