setBackgroundColor does not work after Proguard - android

I am working on finishing up a project, and it's my first time using Proguard. I have a method to set background colors for the activity itself as well as some buttons. Before Proguard everything works fine. After Proguard the colors aren't set.
Before:
public void setBackgroundColor(String color, View background){
String id = "1";
try {
ColorId myObject = new ColorId();
Method method = ColorId.class.getMethod(color);
id = (String) method.invoke(myObject);
} catch (Exception e) {
e.printStackTrace();
id = "1";
}
int thisColor = Integer.valueOf(id);
switch(thisColor) {
case 0://black
background.setBackgroundColor(0xff000000);
break;
case 1://white
background.setBackgroundColor(0xffffffff);
break;
case 2://red
background.setBackgroundColor(0xffCC0000);
break;
...
default:
background.setBackgroundColor(0xff0099cc);
break;
}
}
After:
public void a(String paramString, View paramView){
try {
c localc = new c();
str = (String)c.class.getMethod(paramString, new Class[0]).invoke(localc, new Object[0]);
switch (Integer.valueOf(str).intValue()){
default:
paramView.setBackgroundColor(-16737844);
return;
}
}catch (Exception localException){
for (;;){
localException.printStackTrace();
String str = "1";
}
paramView.setBackgroundColor(-16777216);
return;
}
paramView.setBackgroundColor(-1);
return;
paramView.setBackgroundColor(-3407872);
return;
paramView.setBackgroundColor(-16737844);
return;
paramView.setBackgroundColor(-8355712);
return;
paramView.setBackgroundColor(-6697984);
return;
paramView.setBackgroundColor(-17613);
return;
paramView.setBackgroundColor(-5609780);
return;
paramView.setBackgroundColor(-35700);
}
Can anyone help explain what is happening here, and how I can make this method (and others in the future) work again after obfuscation? To me it looks like Proguard is rearranging things in regards to the switch.

Proguard shortens code by renaming classes and methods to have shorter names and by removing code that isn't referred to. Your code doesn't work because Proguard renamed or removed the ColorId methods black(), white(), and red(). To use reflection, you'd need to add Proguard keep directives to tell it to keep these methods and to keep their original names.
I don't have an explanation for why the "after" code's switch statement is messed up. Are you sure you decompiled it properly?
Why is the "before" code so convoluted? It uses reflection to look up a method by color name, then calls it to translate the color name to a String, parses the String to get an integer code, boxes the integer code into an Integer, unboxes it, uses a switch statement to pick a color value, then sets the background color, replicating the background.setBackgroundColor() call in each of the switch branches (breaking the DRY principle).
Reflection is an extreme tool to use in special cases like dynamically loaded code.
It'd be simpler, faster, and clearer to look up the color name in a HashMap:
static final int DEFAULT_COLOR = 0xff0099cc;
static final Map<String, Integer> colors = new HashMap<String, Integer>();
static {
colors.put("black", 0xff000000);
colors.put("white", 0xffffffff);
colors.put("red", 0xffCC0000);
}
public void setBackgroundColor(String color, View view) {
Integer colorInteger = colors.get(color);
int colorValue = colorInteger == null ? DEFAULT_COLOR : colorInteger.intValue();
view.setBackgroundColor(colorValue);
}
This HashMap is a good choice if the color has to be passed in as a string name. But if you can change the color argument, an enum would be more type safe, simpler, and faster:
public enum Color {
BLACK(0xff000000), WHITE(0xffffffff), RED(0xffCC0000), DEFAULT(0xff0099cc);
final int value;
Color(int value) { this.value = value; }
}
public void setBackgroundColor(Color color, View view) {
view.setBackgroundColor(color.value);
}
[It's better to define all your color values in an Android resource file (colors.xml). You can look them up by resource ID number.]

What is your proguard setup? Post your proguard-project.txt file (or proguard.cfg if you're using the old method). I'd suggest turning off obfuscation to see more clearly how the code is being changed. Use '-dontobfuscate'.
The 'after' code looks odd. Are you using the optimization common configuration file (proguard-android-optimize.txt)? If so, try to use without optimization to reduce how much your code is being modified.

Related

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?
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 :-)
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 :-)
I believe you're looking looking for getResources().getDimension().
Source: http://developer.android.com/reference/android/content/res/Resources.html#getDimension%28int%29
Assuming xml node after parsing your data, try the following
Element element = null; //It is your root node.
NamedNodeMap attrib = (NamedNodeMap) element;
int numAttrs = attrib.getLength ();
for (int i = 0; i < numAttrs; i++) {
Attr attr = (Attr) attrib.item (i);
String attrName = attr.getNodeName ();
String attrValue = attr.getNodeValue ();
System.out.println ("Found attribute: " + attrName + " with value: " + attrValue);
}

Getting Int from EditText causes error?

So first of all sorry if this has already been asked and answered before, I couldn't find anything relating to my issue.
So I'm working on a project for college and I need to get int values from EditText widgets. I was told to use parseInt to do this however when running my program, that line of code causes the application to crash. I don't know what I'm doing wrong, I'm still very new to android development, thanks for the help :)
public void Calculate (View view)
{
int MilesTravelled;
int FuelUsed;
int MPG;
/* the two lines below are what cause the application to crash */
MilesTravelled = Integer.parseInt(txtMilesTravelled.getText().toString());
FuelUsed = Integer.parseInt(txtFuelUsed.getText().toString());
FuelUsed = (int) (FuelUsed / 4.55);
MPG = MilesTravelled / FuelUsed;
lblMPG.setText(FuelUsed);
}
Do you have this in the onCreate() function?
EditText txtMilesTravelled = (EditText) findViewById(R.id.YourEditText);
But I think you mixed Integer and int. They are not the same:
See this link!
First of all, don't capitalize the first letter of an variables or method names. Following the Java coding conventions, only do that for classes.
What is probably causing your app to crash is you trying to set the text of a label to an integer. The setText method for a TextView needs to take in a string.
So change:
lblMPG.setText(FuelUsed);
to:
lblMPG.setText(String.valueOf(FuelUsed));
Otherwise it might be that it's trying to parse a non-numerical string to an integer.
For exmaple, if the EditText is blank, it will cause your app to crash. To prevent that, try this:
int MilesTravelled = 0, FuelUsed = 0;
try {
MilesTravelled = Integer.parseInt(txtMilesTravelled.getText().toString());
FuelUsed = Integer.parseInt(txtFuelUsed.getText().toString());
} catch (NumberFormatException nfe) {
Toast.makeText(getApplicationContext(), "Error NFE!", 0).show();
nfe.printStackTrace();
}
This way, it will catch a NumberFormatException error (parsing a string to an integer that can't be represented as an integer, such as "hello"). If it catches the error, it will toast that an error has occurred and your integer variables will remain 0.
Or you could just test if the strings contain only digits using the following regex:
int MilesTravelled = 0, FuelUsed = 0;
if (txtMilesTravelled.getText().toString().matches("[0-9]+")) {
MilesTravelled = Integer.parseInt(txtMilesTravelled.getText().toString());
} else {
// contains characters that are not digits
}
if (txtFuelUsed.getText().toString().matches("[0-9]+")) {
FuelUsed = Integer.parseInt(txtFuelUsed.getText().toString());
} else {
// contains characters that are not digits
}
If that's not the problem, then make sure you define your variables properly.
txtMilesTravelled and txtFuelUsed should be EditText:
EditText txtMilesTravelled = (EditText)findViewById(R.id.txtMilesTravelled);
EditText txtFuelUsed = (EditText)findViewById(R.id.txtFuelUsed);
And make sure that your R.id.editText actually exists on your layout and that the IDs are the correct ones.
Last thing, make sure FuelUsed is not 0 before calculating MPG because then you are dividing by 0:
int MPG = 0;
if (FuelUsed != 0) {
MPG = MilesTravelled / FuelUsed;
}
I am assuming that you're entering perfect integers in the EditTexts. It might be a good idea to use the trim function txtMilesTravelled.getText().toString().trim() before using parseInt.
However, I think the major problem is here : lblMPG.setText(FuelUsed);
FuelUsed is an integral value, when you pass an integer to setText(), it looks for a string resource with that integral value. So you should be passing a String to the setText() method.
Use : lblMPG.setText(Integer.toString(FuelUsed));

Resources.GetIdentifier() returning 0 for layout (Android)

I'm attempting to get the int resource id for a layout resource by name, using Resources.GetIdentifier() of the Android API, but it returns 0. I'm using c#/monodroid/Xamarin, but regular java Android knowledge would apply too I suspect. Here's my code:
int resId = Resources.GetIdentifier(typeName, "layout", _activity.PackageName);
Where typeName = "FrmMain", and in my project I have the file "Resources/Layout/FrmMain.axml". Any ideas?
This is old, but for everyone getting this problem, I think it is because the resource name should be in lower case, so:
int resId = Resources.GetIdentifier("FrmMain", "layout", _activity.PackageName);
does not work, but:
int resId = Resources.GetIdentifier("frmmain", nameof(Resource.Layout).ToLower(), _activity.PackageName);
should work
I don't know why that's failing, but wouldn't something like Resource.Layout.FrmMain achieve what you're after?
edit:
According to this answer, you can (and should) use reflection to achieve what you're after, so I think you would try something like this:
var resourceId = (int)typeof(Resource.Layout).GetField(typeName).GetValue(null);
which does seem to work on my app and should get what you're after.
In my case, this issue came up when I had to upgrade the target SDK due to google's new policy since November, 2018.
I had to display some strings according to the server response code (ex : api_res_001_suc), but it did not work on the upgraded version.
The overall version, about 22 as I recall, had to be changed to 27.
The cause of the issue seems to be the default translation stuff. When I put all the default translation for every string, it worked.
My code is,
getResources().getIdentifier(resName, "string", "packageName");
I've created a ResourceHelper class to handle this situation. Here is the code:
public static class ResourceHelper
{
public static int FindId(string resourceId)
{
var type = typeof(Resource.Id);
var field = type.GetField(resourceId);
return (int)field.GetRawConstantValue();
}
public static int FindLayout(string layoutName)
{
var type = typeof(Resource.Layout);
var field = type.GetField(layoutName);
return (int)field.GetRawConstantValue();
}
public static int FindMenu(string menuName)
{
var type = typeof(Resource.Menu);
var field = type.GetField(menuName);
return (int)field.GetRawConstantValue();
}
}
Actually I'm improving it because I need to use it from another Assembly and it's restricted to work in the same Assembly of the Droid App. I'm thinking about put a generic method (or an Extension one) to do this. Here is a draft of my idea:
public static int FindResource<T>(string resourceName)
{
var type = typeof(T);
var field = type.GetField(resourceName);
return (int)field.GetRawConstantValue();
}
Hope it can help you.

UriMatcher pattern for arbitrary strings, not URI's

I can't help noticing that I'm using quite a lot of string comparisons while parsing a well defined XML file in Android (with a XmlPullParser).
As of now it typically looks something like this (somewhat simplified):
...
tag = parser.getName().toLowerCase();
if ("tag1".equals(tag)) {
// Do something with the state machine
}
else if ("tag2".equals(tag)) {
// Do something else with the state machine
}
...
else if ("tag23".equals(tag)) {
// Do something more with the state machine
}
What I would like to have instead is something like this (where StringMatcher would be the hypothetical happy-maker for me):
private static final StringMatcher tagMatcher = new StringMatcher(StringMatcher.NO_MATCH);
static {
tagMatcher.addString("tag1", 1);
tagMatcher.addString("tag2", 2);
....
tagMatcher.addString("tag23", 23);
}
...
tag = parser.getName().toLowerCase();
switch (tagMatcher.match(tag)) {
case 1:
// Do something with the state machine
break;
case 2:
// Do something else with the state machine
break;
...
case 23:
// Do something more with the state machine
break;
default:
Log.e("PARSER", "Unexpected tag: " + tag);
break;
}
As you see I would like a UriMatcher pattern applied to my XML file tags. Do any of you know of such a class I can use in Android? Any other fast filtering on strings would do as well (it would be neat, though, if the UriMatcher-pattern could be reused).
So far I've been looking at regular expressions but I'm not really sure I can fit it to my needs (I would like a switch - case style test) and, of course, the regular string comparison as shown in above example.
Cheers,
-- dbm
You can either use a HashMap since that does not need to iterate over the whole array to find the match value
private static final HashMap<String, Integer> tagMatcher =
new HashMap<String, Integer>();
static {
tagMatcher.put("tag1", 1);
tagMatcher.put("tag2", 2);
tagMatcher.put("tag23", 23);
}
private void parse (String node) {
Integer value = tagMatcher.get(node);
int match = value != null ? value.intValue() : 0;
switch (match) {
case 1:
// etc
break;
case 0: // no match
break;
}
}
or you can use a SparseIntArray using the same hash approach. Advantage here is that you don't need to box int into Integer which should result in a slight speed / memory advantage.
private static final SparseIntArray tagMatcher2 = new SparseIntArray();
private static void put(String key, int value) {
tagMatcher2.put(key.hashCode(), value);
}
private static int get(String key) {
return tagMatcher2.get(key.hashCode());
}
static {
put("tag1", 1);
put("tag2", 2);
put("tag23", 23);
}
private void parse2 (String node) {
switch (get(node)) {
case 1:
// etc
break;
case 0: // no match
break;
}
}
This is doing binary search instead of iterating over the whole thing like SparseArray#indexOfValue(t) does. Note that there is a chance of hash collisions in this approach.
I think using an approach like that is faster than a long chain of if (equals) else if (equals) for larger amounts of comparisons. The if .. else if approach needs to check String.equals() every time which boils down to comparing all characters of the strings while a hash based approach needs to calculate the hash value just once and can do a binary search over all known hash values then.
Use a SparseArray
static{
tagmatcher.append(0, "tag1");
tagmatcher.append(1, "tag2");
}
switch(tagmatcher.keyAt(tagmatcher.indexOfValue(tag))){
case 0:
break;
case 1:
break
}
But if you are going to add consecutive indexes you can always use an ArrayList

IfElse or regex

Which one of these would be the best way to do this when you have very long IfElse?
if (text.contains("text"))
{
// do the thing
}
else if (text.contains("foo"))
{
// do the thing
}
else if (text.contains("bar"))
{
// do the thing
}else ...
Or
if (text.contains("text") || text.contains("foo") || ...)
{
// do the thing
}
Or maybe
Pattern pattern = Pattern.compile("(text)|(foo)|(bar)|...");
Matcher matcher = pattern.matcher(text);
if(matcher.find())
{
// do the thing
}
And I mean ONLY when you have to check a lot of these. Thanks!
I would personally use a set as I think it is easier to read and the contains will be efficient in O(1):
Set<String> keywords = new HashSet<String>();
keywords.add("text");
keywords.add("foo");
keywords.add("bar");
if(keywords.contains(text)) {
//do your thing
}
And if you like it compact, you can also write:
Set<String> keywords = new HashSet<String>(Arrays.asList("text", "foo", "bar"));
if(keywords.contains(text)) {
//do your thing
}
And finally, if you always use the same list, you can make keywords private static final instead of recreating it each time you run the method.
EDIT
Following a comment, it is true that what is above is equivalent to using a condition with text.equals("xxx"), not text.contains("xxx"). If you really meant to use contains, then you would have to iterate over the set and test each string, but it becomes an O(n) operation:
for (String key : keywords) {
if (text.contains(key)) {
//do your stuff
break;
}
}
Usually long If else statements are replaced with case statements, but this is not always possible. If I where to recommend, I would go for the second option, option 1 will give you a bunch of If else if else statements which do the same thing while for the third case, regular expressions tend to grow pretty large pretty fast.
Again depending on how much alot is, it could eventually be better to just throw all your strings in a data structure and iterate over it to see if the element is in it.
String[] storage = {
"text",
"foo",
"bar",
"more text"
};
for(int i=0; i < storage.length(); i++){
//Do Something
}
Does this help?

Categories

Resources