All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.lint.checks.RegistrationDetector Maven / Gradle / Ivy

There is a newer version: 25.3.0
Show newest version
/*
 * 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 static com.android.SdkConstants.ANDROID_APP_ACTIVITY;
import static com.android.SdkConstants.ANDROID_APP_SERVICE;
import static com.android.SdkConstants.ANDROID_CONTENT_BROADCAST_RECEIVER;
import static com.android.SdkConstants.ANDROID_CONTENT_CONTENT_PROVIDER;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.TAG_ACTIVITY;
import static com.android.SdkConstants.TAG_PROVIDER;
import static com.android.SdkConstants.TAG_RECEIVER;
import static com.android.SdkConstants.TAG_SERVICE;

import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Detector.ClassScanner;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LayoutDetector;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import com.android.tools.lint.detector.api.XmlContext;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.w3c.dom.Element;

import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Map.Entry;

/**
 * Checks for missing manifest registrations for activities, services etc
 * and also makes sure that they are registered with the correct tag
 * 

* TODO: Rewrite as Java visitor! */ public class RegistrationDetector extends LayoutDetector implements ClassScanner { /** Unregistered activities and services */ public static final Issue ISSUE = Issue.create( "Registered", //$NON-NLS-1$ "Class is not registered in the manifest", "Activities, services and content providers should be registered in the " + "`AndroidManifest.xml` file using ``, `` and `` tags.\n" + "\n" + "If your activity is simply a parent class intended to be subclassed by other " + "\"real\" activities, make it an abstract class.", Category.CORRECTNESS, 6, Severity.WARNING, new Implementation( RegistrationDetector.class, EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE))) .addMoreInfo( "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$ protected Multimap mManifestRegistrations; /** Constructs a new {@link RegistrationDetector} */ public RegistrationDetector() { } @NonNull @Override public Speed getSpeed() { return Speed.FAST; } // ---- Implements XmlScanner ---- @Override public Collection getApplicableElements() { return Arrays.asList(sTags); } @Override public void visitElement(@NonNull XmlContext context, @NonNull Element element) { String fqcn = getFqcn(context, element); String tag = element.getTagName(); String frameworkClass = tagToClass(tag); if (frameworkClass != null) { String signature = ClassContext.getInternalName(fqcn); if (mManifestRegistrations == null) { mManifestRegistrations = ArrayListMultimap.create(4, 8); } mManifestRegistrations.put(frameworkClass, signature); if (signature.indexOf('$') != -1) { // The internal name contains a $ which means it's an inner class. // The conversion from fqcn to internal name is a bit ambiguous: // "a.b.C.D" usually means "inner class D in class C in package a.b". // However, it can (see issue 31592) also mean class D in package "a.b.C". // Place *both* of these possibilities in the registered map, since this // is only used to check that an activity is registered, not the other way // (so it's okay to have entries there that do not correspond to real classes). signature = signature.replace('$', '/'); mManifestRegistrations.put(frameworkClass, signature); } } } /** * Returns the fully qualified class name for a manifest entry element that * specifies a name attribute * * @param context the query context providing the project * @param element the element * @return the fully qualified class name */ @NonNull private static String getFqcn(@NonNull XmlContext context, @NonNull Element element) { String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME); if (className.startsWith(".")) { //$NON-NLS-1$ return context.getMainProject().getPackage() + className; } else if (className.indexOf('.') == -1) { // According to the manifest element documentation, this is not // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html ) // but it appears in manifest files and appears to be supported by the runtime // so handle this in code as well: return context.getMainProject().getPackage() + '.' + className; } // else: the class name is already a fully qualified class name return className; } // ---- Implements ClassScanner ---- @Override public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) { // Abstract classes do not need to be registered if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) { return; } String curr = classNode.name; int lastIndex = curr.lastIndexOf('$'); if (lastIndex != -1 && lastIndex < curr.length() - 1) { if (Character.isDigit(curr.charAt(lastIndex+1))) { // Anonymous inner class, doesn't need to be registered return; } } while (curr != null) { for (String s : sClasses) { if (curr.equals(s)) { Collection registered = mManifestRegistrations != null ? mManifestRegistrations.get(curr) : null; if (registered == null || !registered.contains(classNode.name)) { report(context, classNode, curr); } } } curr = context.getDriver().getSuperClass(curr); } } private void report(ClassContext context, ClassNode classNode, String curr) { String tag = classToTag(curr); String className = ClassContext.createSignature(classNode.name, null, null); String wrongClass = null; // The framework class this class actually extends if (mManifestRegistrations != null) { Collection> entries = mManifestRegistrations.entries(); for (Entry entry : entries) { if (entry.getValue().equals(classNode.name)) { wrongClass = entry.getKey(); break; } } } if (wrongClass != null) { Location location = context.getLocation(classNode); context.report( ISSUE, location, String.format( "`%1$s` is a `<%2$s>` but is registered in the manifest as a `<%3$s>`", className, tag, classToTag(wrongClass))); } else if (!tag.equals(TAG_RECEIVER)) { // don't need to be registered if (context.getMainProject().isGradleProject()) { // Disabled for now; we need to formalize the difference between // the *manifest* package and the variant package, since in some contexts // (such as manifest registrations) we should be using the manifest package, // not the gradle package return; } Location location = context.getLocation(classNode); context.report( ISSUE, location, String.format( "The `<%1$s> %2$s` is not registered in the manifest", tag, className)); } } /** The manifest tags we care about */ private static final String[] sTags = new String[] { TAG_ACTIVITY, TAG_SERVICE, TAG_RECEIVER, TAG_PROVIDER, // Keep synchronized with {@link #sClasses} }; /** The corresponding framework classes that the tags in {@link #sTags} should extend */ private static final String[] sClasses = new String[] { ANDROID_APP_ACTIVITY, ANDROID_APP_SERVICE, ANDROID_CONTENT_BROADCAST_RECEIVER, ANDROID_CONTENT_CONTENT_PROVIDER, // Keep synchronized with {@link #sTags} }; /** Looks up the corresponding framework class a given manifest tag's class should extend */ private static String tagToClass(String tag) { for (int i = 0, n = sTags.length; i < n; i++) { if (sTags[i].equals(tag)) { return sClasses[i]; } } return null; } /** Looks up the tag a given framework class should be registered with */ protected static String classToTag(String className) { for (int i = 0, n = sClasses.length; i < n; i++) { if (sClasses[i].equals(className)) { return sTags[i]; } } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy