Wednesday 22 February 2017

,

Writing Custom Lint for Android Studio

Post By - Tanmay | 2/22/2017 03:05:00 am





Android lints are used to analyze your code for correctness, performance, security, typography and usability; and it has proven itself useful from time to time. In an android studio, there are already some predefined lint rules, which checks for spelling mistakes, possible bugs, performance wise customisations etc., but to follow a standard within a team, we need custom lint rules.


e.g.: in some projects, developers are asked to use a utility class to access SharedPreference, but few of them create a new instance of it every time. To check such thing, we create custom lint rules.

Reference: This post is an extension to Matt Compton’s post Building Custom Lint Checks in Android. In this post, I will just mention few steps to create a custom lint rule. Please refer to Matt’s post for a complete overview.

For making custom lint rules, you need a separate project, you can, fork this git repository, and follow steps to create a rule.

  1. To create a lint rule, create a class in detectors package, name it SharedPreferenceClass
  2. We are going to make the rule mentioned in the example, so, we need to check the uses of SharedPreferences.Editor everywhere except Utils.java

  3. Extend Detector class and implement Detector.JavaScanner (because we’re going to scan through all the Java files. You can also implement ClassScanner and XmlScanner, based on your requirement. The class will look something like this:


     public class SharedPreferenceDetector extends Detector implements Detector.JavaScanner {  
     }  
    

  4. Define the string you want to search for

     private static final String SP_MATCHER_STRING = "SharedPreferences.Editor";  
    


  5. For implementing the issue tracker, we need to define detector class and scope.

     private static final Class<? extends Detector> DETECTOR_CLASS = SharedPreferenceDetector.class;  
     private static final EnumSet<Scope> DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE;  
    



    Detector class is, you can say the engine, and scope is where we want to search. You can search
    1. JAVA_FILE
    2. RESOURCE_FILE
    3. RESOURCE_FOLDER
    4. GRADLE etc.

      We are going to search for JAVA_FILE_SCOPE

  6. Initialise the implementation:

     private static final Implementation IMPLEMENTATION = new Implementation( DETECTOR_CLASS, DETECTOR_SCOPE );  
    

  7. Now you need to define Issue properties, for example, we need 

     private static final String ISSUE_ID = "SharedPreferenceUtils";  
     private static final String ISSUE_DESCRIPTION = "Shared Preference Class Used";  
     private static final String ISSUE_EXPLANATION = "Using the Shared Preference Class is not secure. Consider using our Utils.java for such purpose";  
     private static final Category ISSUE_CATEGORY = Category.CORRECTNESS;  
     private static final int ISSUE_PRIORITY = 8;  
     private static final Severity ISSUE_SEVERITY = Severity.WARNING;  
    

    1. ISSUE_ID should be always unique
      ISSUE_DESCRIPTION, the title you want to display
      ISSUE_EXPLANATION, message you want to display
      ISSUE_CATEGORY, same I mentioned earlier, I am choosing it to be correctness
      ISSUE_PRIORITY, it is self-explanatory
      ISSUE_SEVERITY, there are FATAL, WARNING, ERROR, INFORMATION, IGNORE
    2. Now create an ISSUE object:
      1. public static final Issue ISSUE = Issue.create(
      2.             ISSUE_ID,
      3.             ISSUE_DESCRIPTION,
      4.             ISSUE_EXPLANATION,
      5.             ISSUE_CATEGORY,
      6.             ISSUE_PRIORITY,
      7.             ISSUE_SEVERITY,
      8.             IMPLEMENTATION
      9.     );
    3. Create a constructor of course,

       public SharedPreferenceDetector() {}  
      


    4. getApplicableNodeTypes() is used to check the object type.

       @Override  
         public List<Class<? extends Node>> getApplicableNodeTypes() {  
           return null;  
         }  
      
    5. There is an appliesTo method in the code you forked. Please read this issue tracker post. “appliesTo is a leftover from the beginning of lint when it was just passing through the project, file by file, and letting each detector take a look at the file.”

      So, I am leaving appliesTo method out of the code.


    6. The fun part: we will create a java visitor by overriding the method from JavaScanner interface, and using context.file.getName()  we will match if file is Utils.java, don’t scan it

Now, we need to register it with CustomIssueRegistry. Add your detector in already present Issue array. The complete SharedPreferenceDetector.java will be:



 import com.android.annotations.NonNull;  
 import com.android.ddmlib.Log;  
 import com.android.tools.lint.detector.api.Category;  
 import com.android.tools.lint.detector.api.Context;  
 import com.android.tools.lint.detector.api.Detector;  
 import com.android.tools.lint.detector.api.Implementation;  
 import com.android.tools.lint.detector.api.Issue;  
 import com.android.tools.lint.detector.api.JavaContext;  
 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.TextFormat;  
 import java.io.File;  
 import java.util.EnumSet;  
 import java.util.List;  
 import lombok.ast.AstVisitor;  
 import lombok.ast.Node;  
 /**  
  * Created by tanmaybaranwal on 21/02/17.  
  */  
 public class SharedPreferenceDetector extends Detector implements Detector.JavaScanner {  
   private static final String SP_MATCHER_STRING = "SharedPreferences.Editor";  
   private static final Class<? extends Detector> DETECTOR_CLASS = SharedPreferenceDetector.class;  
   private static final EnumSet<Scope> DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE;  
   public static String fileName;  
   private static final Implementation IMPLEMENTATION = new Implementation(  
       DETECTOR_CLASS,  
       DETECTOR_SCOPE  
   );  
   private static final String ISSUE_ID = "SharedPreferenceUtils";  
   private static final String ISSUE_DESCRIPTION = "Shared Preference Class Used";  
   private static final String ISSUE_EXPLANATION = "Using the Shared Preference Class is not secure. Consider using our Utils.java for such purpose";  
   private static final Category ISSUE_CATEGORY = Category.CORRECTNESS;  
   private static final int ISSUE_PRIORITY = 8;  
   private static final Severity ISSUE_SEVERITY = Severity.WARNING;  
   public static final Issue ISSUE = Issue.create(  
       ISSUE_ID,  
       ISSUE_DESCRIPTION,  
       ISSUE_EXPLANATION,  
       ISSUE_CATEGORY,  
       ISSUE_PRIORITY,  
       ISSUE_SEVERITY,  
       IMPLEMENTATION  
   );  
   /**  
    * Constructs a new {@link SharedPreferenceDetector} check  
    */  
   public SharedPreferenceDetector() {  
   }  
   @Override  
   public List<Class<? extends Node>> getApplicableNodeTypes() {  
     return null;  
   }  
   @Override  
   public AstVisitor createJavaVisitor(@NonNull JavaContext context) {  
     String source = context.getContents();  
     //Leave the Utils.java file  
     if(context.file.getName().equals("Utils.java")){  
       return null;  
     }  
     // Check validity of source  
     if (source == null) {  
       return null;  
     }  
     // Check for uses of to-dos  
     int index = source.indexOf(SP_MATCHER_STRING);  
     for (int i = index; i >= 0; i = source.indexOf(SP_MATCHER_STRING, i + 1)) {  
       Location location = Location.create(context.file, source, i, i + SP_MATCHER_STRING.length());  
       context.report(ISSUE, location, ISSUE.getBriefDescription(TextFormat.TEXT));  
     }  
     return null;  
   }  
 }  


To test the detector, we need a test file. The repo contains a package named test, you can make a class in that like this one. (Please find comments to understand the modules).



 package com.bignerdranch.linette.detectors;  
 import com.android.tools.lint.detector.api.Detector;  
 import com.android.tools.lint.detector.api.Issue;  
 import com.android.tools.lint.detector.api.TextFormat;  
 import com.bignerdranch.linette.AbstractDetectorTest;  
 import java.util.Arrays;  
 import java.util.List;  
 /**  
  * Created by tanmaybaranwal on 21/02/17.  
  */  
 public class SharedPreferenceDetectorTest extends AbstractDetectorTest{  
   @Override  
   protected Detector getDetector() {  
     return new SharedPreferenceDetector();  
   }  
   @Override  
   protected List<Issue> getIssues() {  
     return Arrays.asList(SharedPreferenceDetector.ISSUE);  
   }  
   @Override  
   protected String getTestResourceDirectory() {  
     return "shared";  
   }  
   /**  
    * Test that an empty java file has no warnings.  
    */  
   public void testEmptyCase() throws Exception {  
     String file = "EmptyTestCase.java";  
     assertEquals(  
         NO_WARNINGS,  
         lintFiles(file)  
     );  
   }  
   /**  
    * Test that an Utils.java java file has no warnings.  
    */  
   public void testUtilsCase() throws Exception {  
     String file = "Utils.java";  
     assertEquals(  
         NO_WARNINGS,  
         lintFiles(file)  
     );  
   }  
   /**  
    * Test that a java file with a to-do has a warning.  
    */  
   public void testSharedPrefCase() throws Exception {  
     String file = "SharedPreferenceTestCase.java";  
     String warningMessage = file  
         + ":7: Warning: "  
         + SharedPreferenceDetector.ISSUE.getBriefDescription(TextFormat.TEXT)  
         + " ["  
         + SharedPreferenceDetector.ISSUE.getId()  
         + "]\n"  
         + "  SharedPreferences.Editor editor = mSharedPreferences.edit();\n"  
         + "  ~~~~~~~~~~~~~~~~~~~~~~~~\n"  
         + "0 errors, 1 warnings\n";  
     assertEquals(  
         warningMessage,  
         lintFiles(file)  
     );  
   }  
 }  


Run the lint:

  1. for MacOS users, open terminal in the android studio, do $chmod 755 gradlew
  2. $ ./gradlew clean build test install

You will be able to check the success report in the logged link. To check it

  1. Open terminal
  2. Set the path at SDK/tools
  3. lint ——show <issue_id>

To include lint in a project separately

  1. navigate to /Users/<user_name>/.android/lint/<file.jar>
  2. Copy the jar file, paste it in the lib folder of your project
  3. Add the file dependency in app’s build.gradle
  4. In the gradle, write the lint options:
     lintOptions{  
         abortOnError false //if build has to abort  
         check ‘SharedPreferenceUtils' //to check one custom rule  
         showAll true //show report  
         textReport true  
         textOutput ‘stdout' //shows report in message  
       }  
    

  1. Run the lint using $ ./gradlew lint


While building the project, I got “Error: Error converting bytecode to dex: Cause: Dex cannot parse version 52 bytecode.”. To overcome this, compile your lint project with Java 1.7 to do so, add 

 apply plugin: 'jacoco'  
 dependencies{  
  sourceCompatibility = 1.7  
   targetCompatibility = 1.7  
 }  


in the gradle of lint project.


Thursday 20 August 2015

DIY Home Monitoring System using Raspberry Pi and Webcam

Post By - Tanmay | 8/20/2015 11:49:00 am
Traditional wireless CCTV cameras are cheap but anyone with a wireless receiver can view your signal. On the other hand, IP cameras are secure but they can be quite expensive and usually the video quality is poor — unless you go for a really expensive model.

Necessary hardware:
  1. Raspberry Pi Model B Revision 2.0 (512MB)
  2. Logitech HD Webcam C270 or a similarly compatible usb webcam (list of compatible webcams here).
  3. A usb hub with an external power supply
  4. (optional): a usb extension cable
Step #1: Setup your raspberry pi
Your pi needs to boot a linux operating system in order to run motion. The most popular choice is Raspbian, a debian-based OS that is optimized for pi’s hardware.
To prepare your SD card and install Raspbian I recommend following Adafruit’s excellent tutorials here.
Since you are not going to have your pi connected to a monitor or have a keyboard and mouse, I also recommend enabling Secure Shell (SSH) in your pi so that you can remote control your Raspberry Pi over your local network.
Finally, it is a good thing to force a static IP address so that you can easily find the webcam server even if pi restarts.
To do this, first type from the command prompt:
ifconfig
This reveals your router information. If you have an ethernet connection check out the eth0 bit. If you have a wireless connection check out the wlan0bit. Make a note of the following info:
inet addr – 192.168.1.5 (pi’s IP Address)
Bcast – 192.168.1.255 (broadcast IP range)
Mask – 255.255.255.0 (subnet mask)
then run:
route -n
and note the following:
Gateway Address – 192.168.1.1
then run the following command to edit the network configuration:
sudo nano /etc/network/interfaces
and change the following entry from:
iface wlan0 inet dhcp
to:
iface wlan0 inet static
address 192.168.1.5
netmask 255.255.255.0
gateway 192.168.1.1
network 192.168.1.0
broadcast 192.168.1.255
Press CTRL and together to save and exit nano editor.
If you reboot your pi now you should have a static address.

Step #3: Setup motion
First you need to use rpi-update to add to your raspbian image the initially-missing UVC support:
sudo apt-get install rpi-update
sudo rpi-update
Next you need to upgrade your packages:
sudo apt-get update
sudo apt-get upgrade 
Then you can install motion:
sudo apt-get install motion
Now if you run
lsusb
you should see your camera listed as a usb device, like so:
Bus 001 Device 006: ID 046d:0825 Logitech, Inc. Webcam C270
(If not then perhaps your webcam is not compatible with pi)
Next we proceed to configure motion:
sudo nano /etc/motion/motion.conf
This is a configuration file where you get to define parameters such as the port which motion will run on, or actions that will be triggered when movement is detected.
Here’s a list of the parameters you most likely would want to configure:
  • daemon: set to ON to start motion as a daemon service when pi boots,
  • webcam_localhost: set to OFF so that you can access motion from other computers,
  • stream_port: the port for the video stream (default 8081),
  • control_localhost: set to OFF to be able to update parameters remotely via the web config interface,
  • control_port: the port that you will access the web config interface (default 8080),
  • framerate: number of frames per second to be captured by the webcam. Warning: setting above 5 fps will hammer your pi’s performance!
  • post_capture: specify the number of frames to be captured after motion has been detected.
You also need to edit the following file if you want to run motion as a daemon service:
sudo nano /etc/default/motion
and set start_motion_daemon to YES:
start_motion_daemon=yes
Then start motion by typing:
sudo service motion start
Wait for about 30 seconds for motion to start and then open the video stream from VLC or a similar program that can show video streams. If you use VLC player go to File>Open Network and enter the IP address of your pi followed by the stream_port, for example: 192.168.1.5:8083
Via - The Medium

What's Trending?

Powered by Blogger.

Contact Form