Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving resource values in custom lint rule

I have a large Android codebase and I am writing a custom lint rule that checks whether the values of certain attributes fall within a given range.

For example, I have this component:

<MyCustomComponent
    my:animation_factor="0.7"
    ...>
</MyCustomComponent>

and I want to write a lint rule that alerts developers that values of my:animation_factor >= 1 should be used with caution.

I followed the instructions at http://tools.android.com/tips/lint-custom-rules and managed to retrieve the value of my:animation_factor using this code:

import com.android.tools.lint.detector.api.*;

public class XmlInterpolatorFactorTooHighDetector {

    ....
    @Override
    public Collection<String> getApplicableElements() {
        return ImmutableList.of("MyCustomComponent");
    }

    @Override
    public void visitElement(XmlContext context, Element element) {
        String factor = element.getAttribute("my:animation_factor");
        ...
        if (value.startsWith("@dimen/")) {
            // How do I resolve @dimen/xyz to 1.85?
        } else {
            String value = Float.parseFloat(factor);
        }
    }
}

This code works fine when attributes such as my:animation_factor have literal values (e.g. 0.7).

However, when the attribute value is a resources (e.g. @dimen/standard_anim_factor) then element.getAttribute(...) returns the string value of the attribute instead of the actual resolved value.

For example, when I have a MyCustomComponent that looks like this:

<MyCustomComponent
    my:animation_factor="@dimen/standard_anim_factory"
    ...>
</MyCustomComponent>

and @dimen/standard_anim_factor is defined elsewhere:

<dimen name="standard_anim_factor">1.85</dimen>

then the string factor becomes "@dimen/standard_anim_factor" instead of "1.85".

Is there a way to resolve "@dimen/standard_anim_factor" to the actual value of resource (i.e. "1.85") while processing the MyCustomComponent element?

like image 316
Mike Laren Avatar asked May 20 '15 18:05

Mike Laren


1 Answers

The general problem with the resolution of values is, that they depend on the Android runtime context you are in. There might be several values folders with different concrete values for your key @dimen/standard_anim_factory, so just that you are aware of.

Nevertheless, AFAIK there exist two options:

  • Perform a two phase detection:

    • Phase 1: Scan your resources
    • Scan for your attribute and put it in a list (instead of evaluating it immediately)
    • Scan your dimension values and put them in a list as well
    • Phase 2:
    • override Detector.afterProjectCheck and resolve your attributes by iterating over the two lists filled within phase 1
  • usually the LintUtils class [1] is a perfect spot for that stuff but unfortunately there is no method which resolves dimensions values. However, there is a method called getStyleAttributes which demonstrates how to resolve resource values. So you could write your own convenient method to resolve dimension values:


    private int resolveDimensionValue(String name, Context context){
       LintClient client = context.getDriver().getClient();
       LintProject project = context.getDriver().getProject();
       AbstractResourceRepository resources = client.getProjectResources(project, true);

       return Integer.valueOf(resources.getResourceItem(ResourceType.DIMEN, name).get(0).getResourceValue(false).getValue());
    }

Note: I haven't tested the above code yet. So please see it as theoretical advice :-)

  1. https://android.googlesource.com/platform/tools/base/+/master/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java

Just one more slight advice for your custom Lint rule code, since you are only interested in the attribute:

Instead of doing something like this in visitElement:

String factor = element.getAttribute("my:animation_factor");

...you may want to do something like this:

@Override
public Collection<String> getApplicableAttributes() {
    return ImmutableList.of("my:animation_factor");
}

@Override    
void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute){
    ...
}

But it's just a matter of preference :-)

like image 149
André Diermann Avatar answered Nov 18 '22 23:11

André Diermann