Writing a custom Lint rule

Writing a custom Lint rule sounds like a complicated thing, but actually this is a very easy thing to do, and can benefit your cadebase a great deal.

The best way to do that is to create a separate module, and use the Java plugin instead of the Application/Library plugin that we usually use. 

So, in order to start, create a new module (you can name it customlints), and add this line to the module build.gradle:

apply plugin: 'java-library'
dependencies {
    def lint_version = "26.2.1" //this correspond to gradle plugin version 3.2.1. The rule is to add 23 to the first number
    compileOnly "com.android.tools.lint:lint-api:$lint_version"
    compileOnly "com.android.tools.lint:lint-checks:$lint_version"
}

The next thing we need to do is to create a Detector. The detector is the place where you will put the logic for your lint rule. There is a lot of resources about what you can do there, and for doing complicated things you can look at the source code for the built-in Android Lint rules (here).

Lets use a common example, where you wrote your own Logger class, and you want to have a Lint Check to warn for any use of Log.d instead of your desired SmartLogger.d

So we will create a class named SmartLoggerDetector.java for that purpose:

public class SmartLoggerDetector extends Detector implements Detector.UastScanner {

    @Override 
    public List<String> getApplicableMethodNames() {
        return Arrays.asList("v", "d", "i", "w", "e", "wtf");
    }

    @Override 
    public void visitMethod(@NotNull JavaContext context,@NotNull UCallExpression call,@NotNull PsiMethod method) {
        JavaEvaluator evaluator = context.getEvaluator();

        if (evaluator.isMemberInClass(method, "android.util.Log")) {
            LintFix fix = quickFixIssueLog(call);
            context.report(ISSUE_LOG, call, context.getLocation(call), "Using 'Log' instead of 'SmartLogger'", fix);
        }
    }

    private LintFix quickFixIssueLog(UCallExpression logCall) {
        List<UExpression> arguments = logCall.getValueArguments();
        String methodName = logCall.getMethodName();
        UExpression tag = arguments.get(0);
        String fixSource1 = "SmartLogger." + methodName +  "(" + tag.asSourceString();

        int numArguments = arguments.size();
        if (numArguments == 2) {
            UExpression msgOrThrowable = arguments.get(1);
            fixSource1 +=  ", " + msgOrThrowable.asSourceString() + ")";
        } else if (numArguments == 3) {
            UExpression msg = arguments.get(1);
            UExpression throwable = arguments.get(2);
            fixSource1 += ", " + throwable.asSourceString() + ", " + msg.asSourceString() + ")";
        } else {
            throw new IllegalStateException("android.util.Log overloads should have 2 or 3 arguments");
        }
        String logCallSource = logCall.asSourceString();
        LintFix.GroupBuilder fixGrouper = fix().group();
        fixGrouper.add(fix().replace().text(logCallSource).shortenNames().reformat(true).with(fixSource1).build());
        return fixGrouper.build();
    }

    static Issue[] getIssues() {
        return new Issue[] {
                ISSUE_LOG
        };
    }

    public static final Issue ISSUE_LOG =
            Issue.create("LogNotSmartLogger", "Logging call to Log instead of SmartLogger",
                    "Since SmartLogger is included in the project, it is likely that calls to Log should instead"
                            + " be going to SmartLogger.", Category.CORRECTNESS, 6, Severity.WARNING,
                    new Implementation(SmartLoggerDetector.class, Scope.JAVA_FILE_SCOPE));

}

The method quickFixIssueLog gives you the possibility to integrate a quick fix into Android Studio, but you can skip this part and just put null instead:

 @Override 
    public void visitMethod(@NotNull JavaContext context,@NotNull UCallExpression call,@NotNull PsiMethod method) {
        JavaEvaluator evaluator = context.getEvaluator();
        if (evaluator.isMemberInClass(method, "android.util.Log")) {
            context.report(ISSUE_LOG, call, context.getLocation(call), "Using 'Log' instead of 'Loggy'", null);
        }
    }

Now that our custom Lint check is in place, we need to notify the IDE about it, this is done by adding this rule to Lint registry. For that we will create a class named SmartLoggerIssueRegistry.java:

import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.ApiKt;
import com.android.tools.lint.detector.api.Issue;

import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.List;

public class SmartLoggerIssueRegistry extends IssueRegistry {

    @NotNull
    @Override public List<Issue> getIssues() {
        return Arrays.asList(SmartLoggerDetector.getIssues());
    }

    @Override public int getApi() {
        return ApiKt.CURRENT_API;
    }
}

Now we will use this class in our build.gradle file:

apply plugin: 'java-library'

dependencies {

    def lint_version = "26.2.1" //this correspond to gradle plugin version 3.2.1. The rule is to add 23 to the first number
    compileOnly "com.android.tools.lint:lint-api:$lint_version"
    compileOnly "com.android.tools.lint:lint-checks:$lint_version"
}

jar {
    manifest {
        attributes("Lint-Registry-v2": "com.thedroidboy.customlint.SmartLoggerIssueRegistry")
    }
}

That’s all. In order to consume this rule in another module, we need to hook this module in the client module build.gradle :

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    ...
    lintChecks project(':customlints')
}

And this is how it will look in your client code:

That’s all!

(Or at least this is how it should look like after Android Studio will fix the bug mention here:
In the meanwhile you can use the same workaround mention in the tracker, and put the generated customlint.jar file into ~/.android/lint )

A demo source code can be found here

Additional useful resources:
A video from android-dev-summit-18
Google sample project
Timber lint rules

How to generate status bar icons from text for an Android App

In the Android framework, the status bar icons have to be packaged with the APK and can’t be created dynamically. It’s ok if you need just a few icons, but what if you need a lot of icons? Here you run into a problem. In my case, I needed a set of icons created from text. Think for example that you would want to create an app that put a small icon on the status bar with the current temp and weather state. something like “45 rainy”. In order to accomplish that you need to combine all the combinations for temp + state. How can you do that?

One nice way to do that is to run code that executes a command-line script of an Image editor program called “ImageMagick”. With that program, you can produce automatically icons from a text and do a lot of other manipulations. Of course, the program must be installed on your computer.

In my case, I run this code from Unit test code, this way I didn’t have to deal with build a traditional Java program with a main method.

@Test
public void printIcons() throws IOException, InterruptedException {
    String iconContent;
    String fileName;
    String[] folders = {"/res/drawable-mdpi/", "/res/drawable-hdpi/", "/res/drawable-xhdpi/", "/res/drawable-xxhdpi/"};
    String[] sizes = {"24x24", "36x36", "48x48", "72x72"};
    String[] weatherStates = {"sunny", "rainy", "clear", "snow"};
    for (String state : weatherStates) {
        for (int i = -30; i < 25; i++) {
            iconContent = String.format("label:%d\n%s", i, state);
            for (int j = 0; j < 4; j++) {
                String path = String.format("/Users/******/Documents/test%s", folders[j]);
                Runtime.getRuntime().exec("mkdir -p " + path).waitFor();
                fileName = String.format("/Users/********/Documents/test%s%s_%d.gif", folders[j], state, (i));
                ProcessBuilder pb = new ProcessBuilder("/opt/local/bin/convert", "-background", "transparent", "-fill", "white", "-font", "Times-Bold-Italic",
                        "-size", sizes[j], "-gravity", "center", iconContent, fileName);
                pb.redirectErrorStream(true);
                Process process = pb.start();
                process.waitFor();
            }

        }
    }
}

In windows, the path for the “convert” may be different. Maybe something with “_full_path_/Magick convert”.

You can get the right R.drawable for the icon in this way:

int id = context.getResources().getIdentifier("clear_18", "drawable", context.getPackageName());
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
        .setContentTitle("title")
        .setContentText("content")
        .setSmallIcon(id);

Strip out unused code and libraries with Gradle

Sometimes, we want to write code just for debugging purpose, and we don’t want this code to live in our production. Furthermore, we don’t want to include any libraries that we are using just for debug and testing purposes, in our production apk.

A very good example will be Stetho. Stetho is a great library when we want to browse our database very easily, but it also includes a bunch of code and classes (almost 2K methods) that we have no reason to include in our apk file. Of course, we think that proguard supposed to do this, but if our code includes a method from Stetho library, proguard won’t strip the library out from the apk file, and when we want to shrink our apk as much as possible, all this code not helps us.

So we can do this Gradle. By default, in Android Studio, all the code of our application lives under app/src/main/java folder (and the code for tests in androidTest and test folder), But we can have a different code for different flavours of our app. By default, our app will have 2 flavours: release and debug. You can switch this flavours in the left pane of android studio:

 

 

 

By adding folders to your app directory, you can have 2 different classes with the same signature for each flavour: for release flavour, you need app/src/release/java folder, and for debug flavour you need app/src/debug/java folder.

 

 

 

 

 

Now we can put in this folders duplicate classes, and the build system will take the class belongs to the current flavour. Let’s demonstrate it: We want to use Stetho just in debug flavour, so in our Application we can put this line inside the onCreate():

DebugInitials.init(this);

And in the app/src/debug/java folder we will create DebugInitials.class like this:

public class DebugInitials {
    public static void init(Application application){
        //Exists just in debug build
        Stetho.initializeWithDefaults(application);
    }
}

In the app/src/release/java folder we will create DebugInitials.class like this:

public class DebugInitials {
    public static void init(Application application){

    }
}

This way, Stetho code will live just in debug flavour. Now we want to tell gradle to include Stetho library just in debug. This done very easily:

debugCompile 'com.facebook.stetho:stetho:1.4.2'

And that’s it! Stetho won’t be included any more in our production apk.

Create different values for Debug and Release with Gradle

Sometimes it may happen that you want to have different values in your app for debug and release versions. Of course, you can define it in code and change it yourself but this can become tiring, and cause you to make fatal errors when you forget to switch this values properly.

With Gradle you can do this in very elegant way. Let’s say we want to use some BASE_URL with one url address for testing it when we in debug mode, and a different value when we in release mode. We will define it in the build.gradle file:

android {

    buildTypes {
        debug {
            buildConfigField "String", "BASE_URL", "\"http://192.168.0.129:8090/\""
        }

        release {
            buildConfigField "String", "BASE_URL", "\"http://www.thedroidboy.com/\""
            //rest of code
        }
    }
}

Now we can use this value in our code as gradle will produce a class in our package called BuildConfig.class and we can access its values everywhere in our code:

String APP_BASE_URL = BuildConfig.BASE_URL;

The proper values will be injected based on our build type.

Animator didn’t stop after calling end() method

In the last days I run into a problem: In my layout  I have some Views. To each View I used an Animator object in order to perform infinite animation with them. The animation is pretty simple, and this how I created it:

private static AnimatorSet createLeftTiltAnimator(final View v) {
    AnimatorSet animatorSet = new AnimatorSet();
    final ObjectAnimator scaleX = new ObjectAnimator();
    final ObjectAnimator rotateLeft = new ObjectAnimator();
    scaleX.setPropertyName("scaleX");
    scaleX.setRepeatMode(ValueAnimator.REVERSE);
    scaleX.setRepeatCount(ValueAnimator.INFINITE);
    scaleX.setFloatValues(1f, 0.92f);
    scaleX.setTarget(v);
    rotateLeft.setPropertyName("rotation");
    rotateLeft.setRepeatMode(ValueAnimator.REVERSE);
    rotateLeft.setRepeatCount(ValueAnimator.INFINITE);
    rotateLeft.setFloatValues(0, -7.5f);
    rotateLeft.setTarget(v);
    animatorSet.playTogether(scaleX, rotateLeft);
    animatorSet.setDuration(400);
    return animatorSet;
}

Inside my logic, whenever I needed I just called on that Animator object with:

    animatorSet.start();

Well, it worked well, but the problem was when I wanted to end the animation, after I called:

    animatorSet.end();

Some of the Views just stayed with the animation, and I didn’t realise why it happens.
After some analysing, I figured that on some of the View Animators I called twice:

    animatorSet.start();

In this situation, calling end() just didn’t help. So what the solution to prevent such incidents? Well, actually it is very simple, before calling the start() I just added a check if the Animator not already running:

if (!animatoreSet.isRunning())
    viewHolder.iconAnimatoreSet.start();

View database records in Android Device with Stetho

Today we will talk about pretty cool tool for every Android developer who uses SQLite. And this is the Stetho who developed by Facobook.

With Stetho you can view your SQLite records on the device very easily. All you need to do is add this line to your build.gradle file:

compile 'com.facebook.stetho:stetho:1.4.2'

And add this line to your Activity onCreate() method:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Stetho.initializeWithDefaults(this);
    setContentView(R.layout.activity_main);
}

And that it. When the app is running, you can browse very easily inside database, by opening chrome  in the url:

chrome://inspect/#devices

Then you will see something like that:

 

 

 

 

 

 

 

Then the Developer Tools window will pop up. In the Resources tab, under Web SQL you will be able to find your database:

 

 

 

 

 

 

The records display is something like this:

 

 

 

 

And that’s all!

More details can be found here:
http://facebook.github.io/stetho/

Android : Draw View on top of other application

Let’s say that you want to write an app that waiting in the background of the device and listening to something that going to happen, in order to pop-up some window offering information or button to perform a specific action.  It can be even something simple like URL namespace that the user just copied into the clipboard and you want to offer him to do something with this URL.

In order to do this, you need to use  WindowManager inside a Service . The WindowManager have the ability to draw a View on top of everything else, with the right LayoutParams, and specifically, the right WindowManager.LayoutParams Flags.

Let’s show how we can do this. First thing you have to use this permission:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

And your target API need to be 22 or below, or you will have to deal with permission issues.
Now, you don’t have to have any Activity at all, but for this tutorial, we will use very simple MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        startService(new Intent(this, OverlayButtonService.class));
        finish();
    }
}

Inside the OverlayButtonService (don’t forget to register your Service in the manifest) we have the interesting code. We will use very simple View that we will define in R.layout.button.xml file:

<?xml version="1.0" encoding="utf-8"?>
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:background="@drawable/btn_bg"
    android:src="@android:drawable/ic_input_add"/>

And the OverlayButtonService code is:

public class OverlayButtonService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);
        layoutParams.gravity = Gravity.TOP | Gravity.START;
        layoutParams.x = 50;
        layoutParams.y = 50;
        @SuppressLint("InflateParams")
        View btn = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE))
                .inflate(R.layout.button, null);
        final WindowManager windowManager = ((WindowManager) getSystemService(WINDOW_SERVICE));
        windowManager.addView(btn, layoutParams);
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //here you can do whatever action you want after click
                close(v);
                return false;
            }
        });

    }


    public void close(View view){
        try {
            final WindowManager windowManager = ((WindowManager) getSystemService(WINDOW_SERVICE));
            windowManager.removeView(view);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            stopSelf();
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

What we do is very simple. We define the LayoutParams to draw the Button in the top left corner, and we use FLAG_NOT_FOCUSABLE as we want that the user will be able to keep communicate the screen without we interrupt it.

The output of this code can be something like that:

The orange button is our button and in the moment the user will touch it, the button will disappear.

The full code in this post can be found here:
https://github.com/yshahak/OverlayButton/tree/master

 

How to write simple Android Lock Screen

Hello everyone,

I will start this blog by explaining how you can write simple Lock Screen for Android.

First, it important to understand that officially, Android have built it Lock Screen mechanism, and there isn’t any official API for bypass or replace this Lock Screen.
However, if you want to do that nevertheless, you can use some tricks that can help you achieve your purpose. I hope this post will help other programmers as I know I wasn’t able to find too much information when I tried to build Lock Screen from scratch.

The Lock Screen I’m going to show is quite a real one, as it disables completely the user ability to communicate with the device as long as the Lock Screen is displayed.
In order to do that, I needed to find a way to bypass Home Screen button, and also hide the Status Bar.
We will use very simple UI for this demo:


The basic concept for building your own Lock Screen is to be able to draw your layout on top of any other component in the user device so the user won’t be able to use the device unless he will follow your own security demands, like type in a PIN or swipe left, or any other measure you choose to implement.

So all you need to do is run your Service that will listen to any Screen Off events, and each time this event happens you will push your UI on top of everything.

 

First, there is two Permission must be including in the Manifest.xml file:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

android.permission.SYSTEM_ALERT_WINDOW permission enables you to draw your layout on top of everything in the device.

android.permission.RECEIVE_BOOT_COMPLETED permission needed in order to run your service right after user switch on his device.

Now, the Service need to listen to Intent.ACTION_SCREEN_OFF event. So after the Service will start we will create and register simple BroadcastReciever inside the Service:

BroadcastReceiver screenReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF) && linearLayout == null) {
            init();
        }
    }
};

linearLayout == null is the indicator to check if our layout is already displayed, as we don’t want to draw duplicate layouts again and again.

We will register this receiver in the Service onCreate() method, and will define the LayoutParams we will use for drawing our layout on top of everything:

private LinearLayout linearLayout;
private WindowManager.LayoutParams layoutParams;
private WindowManager windowManager;

@Override
public void onCreate() {
 super.onCreate();
 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
 registerReceiver(screenReceiver, intentFilter);

 windowManager = ((WindowManager) getSystemService(WINDOW_SERVICE));
 layoutParams = new WindowManager.LayoutParams(
 WindowManager.LayoutParams.MATCH_PARENT,
 WindowManager.LayoutParams.MATCH_PARENT,
 WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 
 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION,
 PixelFormat.TRANSLUCENT);
}

WindowManager.LayoutParams.TYPE_SYSTEM_ERROR is a window flag that gives us the ability to put our layout on top of everything.
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN simply makes our layout to be drawn on the entire screen.
WindowManager.LayoutParams.SYSTEM_UI_FLAG_HIDE_NAVIGATION hide the device navigation buttons.

Pay attention: we must target targetSdkVersion not higher than 22! otherwise will get Permission exception.

So right now, each time screen off event will be intercepted by our screenReceiver and linearlayout will be null, we will call our init() method:

private void init() {
    linearLayout = new LinearLayout(this);
    windowManager.addView(linearLayout, layoutParams);
    ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.lock_screen, linearLayout);
    View btnClose = linearLayout.findViewById(R.id.btn_close);
    btnClose.setOnClickListener(this);
}

lock_screen.xml layout is very simple and contains just a simple button:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent">

    <Button
        android:id="@+id/btn_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="close"
        android:layout_gravity="center"/>

</FrameLayout>

Now, all left is to remove all UI component after the user clicked our botton:

@Override
public void onClick(View view) {
    windowManager.removeView(linearLayout);
    linearLayout = null;
}

MainActivity.class will be super simple. It will just trigger the service.

public class MainActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        startService(new Intent(this, LockScreenService.class));
        finish();
    }
}

Hope you enjoyed! The whole code for this demo can be found here:
http://github.com/yshahak/LockScreenDemo