Sunday, 9 August 2015

findViewById, Butter Knife to rescue


As an android developer you must have written a lot of code in onCreate [in Activity] or onCreateView[in Fragment]  to get the reference of views defined in layout xml files. For example to get the reference of username EditText and login Button from fragment_login.xml we do:

    EditText etUsername;
    Button btLogin;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_login, container, false);
 
         etUsername=(EditText) view.findViewById(R.id.edittext_username);
         btLogin=(Button)view.findViewById(R.id.button_login);
 
        return view;

    }

if our application has lot of screen and views, writing such boilerplate code wastes a lot of time and make our classes look ugly.

ButterKnife is a small view injection library from Jake Wharton  which make the whole process easier with very few lines of code. To use ButterKnife  in your app you first need to add the dependency to you app module's gradle.build file as follows

dependencies {
 
    compile 'com.jakewharton:butterknife:7.0.1'
}

Once the dependency is added, you can use the Bind annotation to bind the Views by specifying their xml view ids.


   @Bind(R.id.editTextUsernameFragmentLogin)    EditText editTextUsername;
   @Bind(R.id.buttonLoginFragmentLogin)     Button buttonLogin;
   
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
     
        View view = inflater.inflate(R.layout.fragment_login, container, false);
        ButterKnife.bind(this, view);
     
        return view;
    }

our onCreateView  has become a lot cleaner now. We just has to inform the ButterKnife which Id we want to bind and type and name of the variable and with ButterKnife.bind(this,view) it will do the rest for us.

We can also set the onClickListener on views with OnClick annotation. as

 @OnClick(R.id.buttonLoginFragmentLogin)
  public void onLogin() {
      //onLogin  
    }

Do forget to unbind the views in onDestroyView in case of fragment

    @Override
     public void onDestroyView() {
         super.onDestroyView();
         ButterKnife.unbind(this);
     }


That's it about view injection. Hope this will help you in writing clean code with less effort.

Wednesday, 8 October 2014

AsyncTask and context leaking

AsyncTask is mostly used as inner class of Activity or Fragment. For example:

public class SampleActivity extends Activity
{
    private static SampleActivity instance;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);

        new SampleTask().execute();
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        instance = null;
    }

    @Override
    protected void onResume()
    {
        super.onResume();
        instance = this;
    }

    private class SampleTask extends AsyncTask<Void, Void, String>
    {
        @Override
        protected String doInBackground(Void... params)
        {
            // function calls
            return "dummyResult";
        }

        @Override
        protected void onPostExecute(String result)
        {
            super.onPostExecute(result);

            if(SampleActivity.instance != null)
            {
                onResult(result);
            }
        }

    }

    private void onResult(String result)
    {
        // Update UI
    }

}

The problem with the above approach is that as long as SampleTask is running, SampleActivity will not be garbage collected even after being stopped. SampleTask needs an instance of activity because it is declared inside the SampleActivity.

The right way to use AsyncTask inside Activity is to define it as static. Static members don't need instance of the class so the activity will be garbage collected (after it is stopped) even though if task is running. To update the UI from task we need instance of the activity, and for that we can keep weakRefrence of activity inside the Asynctask. WeakRefrence allow the activity to be garbage collected once it is stopped. The above code can be re-written like this:

public class SampleActivity extends Activity
{
    private static SampleTask task;
   
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);

        task = new SampleTask(this);
        task.execute();
    }
    @Override
    protected void onPause()
    {
        super.onPause();
        if(task != null)
        {
            task.cancel(true);
            task = null;
        }
    }
    private static class SampleTask extends AsyncTask<Void, Void, String>
    {
        WeakReference<SampleActivity> activityRef;

        public SampleTask(SampleActivity activity)
        {
            activityRef = new WeakReference<SampleActivity>(activity);
        }

        @Override
        protected String doInBackground(Void... params)
        {
            // function calls
            return "dummyResult";
        }

        @Override
        protected void onPostExecute(String result)
        {
            super.onPostExecute(result);

            SampleActivity activity = activityRef.get();

            if(activity != null)
            {
                activity.onResult(result);
            }
        }

    }

    private void onResult(String result)
    {
        // update UI
    }

}




Tuesday, 10 June 2014

MediaCodec: Decoding AAC android

Encoding/Decoding for various audio/video formats is now possible in android since
JellyBean. Sample code below shows AAC decoding using MediaCodec API provided in
android JellyBean and above.

Sample InputFile
Sample input file, songwav.mp4 is also created using MediaCodec and MediaMuxer.
Songwav.mp4 file AAC encoded file with following parameters:
 
    MediaFormat outputFormat = MediaFormat.createAudioFormat(
            "audio/mp4a-latm", 44100, 2);
    outputFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
            MediaCodecInfo.CodecProfileLevel.AACObjectLC);
    outputFormat.setInteger(MediaFormat.KEY_BIT_RATE,
            128000);
AAC Decoding
AAC file is decoded using MediaExtractor and MediaCodec. AudioTrack is used to play
the audio while decoding.

String inputfilePath = Environment.getExternalStorageDirectory()
            .getPath() + "/" + "songwav.mp4";
String outputFilePath = Environment.getExternalStorageDirectory()
            .getPath() + "/" + "songwavmp4.pcm";
OutputStream outputStream = new FileOutputStream(outputFilePath);
    MediaCodec codec;
    AudioTrack audioTrack;

// extractor gets information about the stream
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(inputfilePath);

        MediaFormat format = extractor.getTrackFormat(0);
    String mime = format.getString(MediaFormat.KEY_MIME);

    // the actual decoder
    codec = MediaCodec.createDecoderByType(mime);
    codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
    codec.start();
    ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
    ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();

    // get the sample rate to configure AudioTrack
    int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);

    // create our AudioTrack instance
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
            AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
            AudioTrack.getMinBufferSize(sampleRate,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT), AudioTrack.MODE_STREAM);

    // start playing, we will feed you later
    audioTrack.play();
    extractor.selectTrack(0);

    // start decoding
    final long kTimeOutUs = 10000;
    MediaCodec.BufferInfo BufInfo = new MediaCodec.BufferInfo();
    boolean sawInputEOS = false;
    boolean sawOutputEOS = false;

    int inputBufIndex;

    int counter=0;
while (!sawOutputEOS) {


        counter++;
        if (!sawInputEOS) {
            inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
            // Log.d(LOG_TAG, " bufIndexCheck " + bufIndexCheck);
            if (inputBufIndex >= 0) {
                ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];

                int sampleSize = extractor
                        .readSampleData(dstBuf, 0 /* offset */);

                long presentationTimeUs = 0;

                if (sampleSize < 0) {

                    sawInputEOS = true;
                    sampleSize = 0;
                } else {

                    presentationTimeUs = extractor.getSampleTime();
                }
                // can throw illegal state exception (???)

                codec.queueInputBuffer(inputBufIndex, 0 /* offset */,
                        sampleSize, presentationTimeUs,
                        sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM
                                : 0);

                if (!sawInputEOS) {
                    extractor.advance();
                }
            } else {
                Log.e("sohail", "inputBufIndex " + inputBufIndex);
            }
        }

        int res = codec.dequeueOutputBuffer(BufInfo, kTimeOutUs);

        if (res >= 0) {
            Log.i("sohail","decoding: deqOutputBuffer >=0, counter="+counter);
            // Log.d(LOG_TAG, "got frame, size " + info.size + "/" +
            // info.presentationTimeUs);
            if (BufInfo.size > 0) {
                // noOutputCounter = 0;
            }

            int outputBufIndex = res;
            ByteBuffer buf = codecOutputBuffers[outputBufIndex];

            final byte[] chunk = new byte[BufInfo.size];
            buf.get(chunk);
            buf.clear();

            if (chunk.length > 0) {
                // play
                audioTrack.write(chunk, 0, chunk.length);
                // write to file
                outputStream.write(chunk);

            }
            codec.releaseOutputBuffer(outputBufIndex, false /* render */);
            if ((BufInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                Log.i("sohail", "saw output EOS.");
                sawOutputEOS = true;
            }
        } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            codecOutputBuffers = codec.getOutputBuffers();

            Log.i("sohail", "output buffers have changed.");
        } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            MediaFormat oformat = codec.getOutputFormat();

            Log.i("sohail", "output format has changed to " + oformat);
        } else {
            Log.i("sohail", "dequeueOutputBuffer returned " + res);
        }
    }

    Log.d(LOG_TAG, "stopping...");

    // ////////closing
    if (audioTrack != null) {
        audioTrack.flush();
        audioTrack.release();
        audioTrack = null;
    }

    outputStream.flush();
    outputStream.close();

    codec.stop();

Monday, 2 June 2014

Scaling images in onCreate: Android


Android developers mostly face out of memory error when loading images into imageView. Primarily this error is raised because we try to load very large image and there is not much memory available. To prevent this, this is good idea to scale down the image according to imageView height/width. setScaledAvatar() method is doing this scaling.

private void setScaledAvatar(String avatarPath, ImageView imageView) {

        // Get the dimensions of the View
        int targetW = imageView.getWidth();
        int targetH = imageView.getHeight();

        // Get the dimensions of the bitmap
        BitmapFactory.Options bmOptions = new BitmapFactory.Options();

        bmOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(avatarPath, bmOptions);
        int photoW = bmOptions.outWidth;
        int photoH = bmOptions.outHeight;

        if (targetW == 0 || targetH == 0) {
            throw new RuntimeException("ImageView height or width is zero");
        }


        // Determine how much to scale down the image
        int scaleFactor = Math.min(photoW / targetW, photoH / targetH);

        // Decode the image file into a Bitmap sized to fill the View
        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = scaleFactor;
        bmOptions.inPurgeable = true;

        Bitmap bitmap = BitmapFactory.decodeFile(avatarPath, bmOptions);
        imageView.setImageBitmap(bitmap);

   
    }

However if you want to use this method on activity onCreate, this will throw divided by zero exception. why? because in onCreate, view are not yet actually drawn and imageView height and width are zero. To handle this situation,we can getViewTree observer and override addGlobalLayout method to for scalling image according to imageView height/width.
 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main_fragment);

       String path = Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES).getAbsolutePath()
                + "/" + "sample.png";
        ImageView avatar = (ImageView) findViewById(R.id.ivAvatar);

      
        avatar.getViewTreeObserver().addOnGlobalLayoutListener(
                new ViewTreeObserver.OnGlobalLayoutListener() {

                    @Override
                    public void onGlobalLayout() {
                        if (path != null && avatar != null) {

                            setScaledAvatar(path, avatar);
                        }

                    }
                });

    }


Sunday, 1 June 2014

Making three state custom button in android


Today I'll discuss how can we make three state button like repeat song button in android music app.
First of all we need to define xml custom attributes for our custom button. Our repeat button has three states:
  1. Repeat One.
  2. Repeat All.
  3. Repeat Off.
So we need to define xml attributes for:
  1. src_repeat_one => Source drawable to be shown for Repeat One.
  2. src_repeat_all   => Source drawable to be shown for Repeat All.
  3. src_repeat_off  => Source drawable to be shown for Repeat Off.
plus, we also need another xml attribute to set the current state:
     repeat_state => which can be 0, 1 or 2 (off, on , all).

So lets create attr.xml in values folder of our android project.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- repeat state -->
    <attr name="repeat_state">
        <enum name="off" value="0" />
        <enum name="one" value="1" />
        <enum name="all" value="2" />
    </attr>

    <!-- custom repeat button -->
    <declare-styleable name="RepeatButton">
        <attr name="repeat_state" />
        <attr name="src_repeat_off" format="integer" />
        <attr name="src_repeat_one" format="integer" />
        <attr name="src_repeat_all" format="integer" />
    </declare-styleable>

</resources> 


Now, we will make Repeat button by extending the imageButton:

RepeatButton.xml:

public class RepeatButton extends ImageButton {

    private final int MAX_STATES=3;
    int state;
    Drawable srcRepeatOff;
    Drawable srcRepeatOne;
    Drawable srcRepeatAll;
    int repeatState;
    Context context;
    

    public RepeatButton(Context context) {
        super(context);
        this.context=context;

    }

    public RepeatButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.RepeatButton);

        try {

            repeatState = a
                    .getInteger(R.styleable.RepeatButton_repeat_state, 0);
            srcRepeatOff = a
                    .getDrawable(R.styleable.RepeatButton_src_repeat_off);
            srcRepeatOne = a
                    .getDrawable(R.styleable.RepeatButton_src_repeat_one);
            srcRepeatAll = a
                    .getDrawable(R.styleable.RepeatButton_src_repeat_all);

        } catch (Exception e) {

        } finally {
            a.recycle();
        }

        switch (repeatState) {
        case 0:
            this.setBackground(srcRepeatOff);
            break;
        case 1:
            this.setBackground(srcRepeatOne);
            break;
        case 2:
            this.setBackground(srcRepeatAll);
            break;
        default:
            break;

        }
    }

    @Override
    public boolean performClick() {
        super.performClick();
        nextState();
        setStateBackground();
        return true;

    }
    
    private void nextState() {
        state++;

        if (state == MAX_STATES) {
            state = 0;
        }
    }

    private void setStateBackground() {

        switch (state) {
        case 0:
            this.setBackground(srcRepeatOff);
            showButtonText("Repeat Off");
            break;
        case 1:
            this.setBackground(srcRepeatOne);
            showButtonText("Repeat One");
            break;
        case 2:
            this.setBackground(srcRepeatAll);
            showButtonText("Repeat All");
            
            break;
        default:
            break;

        }
    }
    public void showButtonText(String text) {

        Toast.makeText(context, text, Toast.LENGTH_SHORT).show();

    }
    public REPEAT getRepeatState() {

        switch (state) {
        case 0:
            return REPEAT.OFF;
        case 1:
            return REPEAT.ONE;
        case 2:
            return REPEAT.ALL;
        default:
            return REPEAT.OFF;

        }
    }

    public void setRepeatState(REPEAT repeatState) {

        switch (repeatState) {
        case OFF:
            state=0;

            break;
        case ONE:
            state=1;
            break;
        case ALL:
            state=2;
            break;
        default:
            break;
        }

        setStateBackground();
    }
}


We retrieve xml attributes and set the imageButton values accordingly. PerformClick is called whenever user press the button, so we change the button state here. REPEAT is an enum define like this:

public enum REPEAT {
     OFF,ONE,ALL;
}

getRepeatState and setRepeatState methods are not necessary and  provided only to change button state programatically.

We have created all the necessary components of our custom button. Now lets use it in our layout, activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <sohail.aziz.samplebutton.RepeatButton
        android:id="@+id/btRepeat"
        android:layout_width="60dp"
        android:layout_height="40dp"
        android:layout_marginRight="100dp"
        android:padding="5dp"
        app:repeat_state="off"
        app:src_repeat_all="@drawable/repeat_enable"
        app:src_repeat_off="@drawable/repeat_disable"
        app:src_repeat_one="@drawable/repeat_enable_1" />

 . . . . . 

</LinearLayout>


Pay attention to xmlns:app="http://schemas.android.com/apk/res-auto" this is necessary for using custom attributes in your layouts. The layout (with three buttons, different states) will look like this:




Complete source code can be found here three-state-button .
    
     

Wednesday, 6 November 2013

Secure your content provider with SQLCipher


SQLCipher provides encryption of SQLite database files. It encrypts database using AES-256 in CBC mode. SQLCipher supports many platform including android. Here is a basic tutorial for setting up and using SQLCipher in android application.

In this tutorial I'll try to explain how can we secure our Contentprovider with SQLCipher. We need to change few things in our MyDatabase class in order to use SQLCipher. We have to import:

   import net.sqlcipher.database.SQLiteDatabase;
   import net.sqlcipher.database.SQLiteOpenHelper;

instead of

  import android.database.sqlite.SQLiteDatabase;
  import android.database.sqlite.SQLiteOpenHelper;


Method signatures of both libraries  are same so no modification is required in function calls.
Now lets see the MyContentProvider class. Here again we need use SQLCipher libs instead of android's SQLite libs. Import

 import net.sqlcipher.database.SQLiteDatabase;
 import net.sqlcipher.database.SQLiteQueryBuilder;

Instead of

 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteQueryBuilder;


There is a small difference between SQLite and SQLCipher's db.getReadableDatabase and db.getWriteableDatabase functions though.
android's SQLite  functions receives no argument while SQLCipher's functions take (String) password as an argument. This password is used to encrypt when writing and decrypt when reading from database. To put things together, MyContentProvider class will be look like this when using SQLCipher:

package sohail.aziz.mycontentprovider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;

import net.sqlcipher.database.SQLiteDatabase;

import net.sqlcipher.database.SQLiteQueryBuilder;
import android.net.Uri;
import android.util.Log;


public class MyContentProvider extends ContentProvider {

        private MyDatabase mydb;

        private static String dbPassword = "sohail"; 

        @Override
        public boolean onCreate() {
               
                Context ctx = getContext();
                mydb = new MyDatabase(ctx);
                return (mydb == null) ? false : true;
        }

        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
              
                 SQLiteDatabase db = mydb.getWritableDatabase(dbPassword);
                 int token = MyContentDescriptor.URI_MATCHER.match(uri);
                 int count=0;
                 
                 switch(token){
                 case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
                        count= db.delete(MyContentDescriptor.Categories.TABLE_NAME, selection, selectionArgs);
                         break;
                 case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN:
                         count= db.delete(MyContentDescriptor.Transactions.TABLE_NAME, selection, selectionArgs);
                         break;
                 }
                 
                getContext().getContentResolver().notifyChange(uri, null);
                return count;
               
        }

        @Override
        public String getType(Uri uri) {
                // returning self defined mime types
                // to be used by other applications if any
                final int match = MyContentDescriptor.URI_MATCHER.match(uri);
                switch (match) {

                case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
                case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN:
                        return MyContentDescriptor.CONTENT_TYPE_DIR;

                case MyContentDescriptor.Categories.CAT_PATH_FOR_ID_TOKEN:
                case MyContentDescriptor.Transactions.TRAN_PATH_FOR_ID_TOKEN:
                        return MyContentDescriptor.CONTENT_ITEM_TYPE;

                }

                return null;
        }

        @Override
        public Uri insert(Uri uri, ContentValues values) {
               
                Log.d("sohail", "inside insert");
              SQLiteDatabase db =mydb.getWritableDatabase(dbPassword);

                int token = MyContentDescriptor.URI_MATCHER.match(uri);
                switch (token) {
                case MyContentDescriptor.Categories.CAT_PATH_TOKEN: // uri is of
                                                                                                                        // categories table
                        Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
                        long id = db.insert(MyContentDescriptor.Categories.TABLE_NAME,
                                        null, values);
                        // notifying change to content observers
                        getContext().getContentResolver().notifyChange(uri, null);
                        return MyContentDescriptor.Categories.CONTENT_URI.buildUpon()
                                        .appendPath(String.valueOf(id)).build();

                case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN: // uri is of
                                                                                                                                // transaction
                                                                                                                                // table
                        Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString());
                        long idd = db.insert(MyContentDescriptor.Transactions.TABLE_NAME,
                                        null, values);
                        getContext().getContentResolver().notifyChange(uri, null);
                        return MyContentDescriptor.Transactions.CONTENT_URI.buildUpon()
                                        .appendPath(String.valueOf(idd)).build();

                default:
                        throw new UnsupportedOperationException("URI: " + uri
                                        + " not supported.");
                }

        }

        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
               
                Log.d("sohail", "query called");
               SQLiteDatabase db = mydb.getReadableDatabase(dbPassword);
                SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
                Cursor c;
                int token = MyContentDescriptor.URI_MATCHER.match(uri);

                switch (token) {

                case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
                        Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
                        queryBuilder.setTables(MyContentDescriptor.Categories.TABLE_NAME);
                        c = queryBuilder.query(db, projection, selection, selectionArgs,
                                        null, null, sortOrder);
                        return c;

                case MyContentDescriptor.Categories.CAT_PATH_FOR_ID_TOKEN:
                        Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
                        queryBuilder.setTables(MyContentDescriptor.Categories.TABLE_NAME);
                        queryBuilder.appendWhere(MyContentDescriptor.Categories.Cols.cat_id
                                        + "=" + uri.getLastPathSegment());
                        c = queryBuilder.query(db, projection, selection, selectionArgs,
                                        null, null, sortOrder);
                        return c;

                case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN:
                        Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString());
                        queryBuilder.setTables(MyContentDescriptor.Transactions.TABLE_NAME);
                        c = queryBuilder.query(db, projection, selection, selectionArgs,
                                        null, null, sortOrder);
                        return c;
                case MyContentDescriptor.Transactions.TRAN_PATH_FOR_ID_TOKEN:
                        Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString());
                        queryBuilder.setTables(MyContentDescriptor.Transactions.TABLE_NAME);
                        queryBuilder
                                        .appendWhere(MyContentDescriptor.Transactions.Cols.tran_id
                                                        + "=" + uri.getLastPathSegment());
                        c = queryBuilder.query(db, projection, selection, selectionArgs,
                                        null, null, sortOrder);
                        return c;

                default:
                        Log.d("sohail", "no URI MATCHED");
                        return null;
                }

        }

        @Override
        public int update(Uri uri, ContentValues values, String selection,
                        String[] selectionArgs) {
              

        SQLiteDatabase db = mydb.getWritableDatabase(dbPassword);
                 int token = MyContentDescriptor.URI_MATCHER.match(uri);
                 int count=0;
                 
                 switch(token){
                 case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
                        count= db.update(MyContentDescriptor.Categories.TABLE_NAME,values, selection, selectionArgs);
                         break;
                 case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN:
                         count= db.update(MyContentDescriptor.Transactions.TABLE_NAME,values,selection, selectionArgs);
                         break;
                 }
                 
                getContext().getContentResolver().notifyChange(uri, null);
                return count;
               
        }

}


Please note that we use

mydb.getWritableDatabase(dbPassword);    and
mydb.getReadableDatabase(dbPassword);
 
With this slight change the database file is encrypted using password dbPassword. Please do configure the libs and assests as explained on SQLCipher site before running this code.



Monday, 28 October 2013

Custom ContentProvider Permissions and Use from other Apps


A custom content provider was discussed and implemented here earlier. Today I'll discuss how a custom content provider can be used by other applications and how content provider permissions can be defined which other applications need to have in order to use it.

Content Provider Permissions:

Lets start with the provider permissions. Below is the modified Manifest.xml file of the MyContentProvider:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="sohail.aziz.mycontentprovider"
    android:versionCode="1"
    android:versionName="1.0" >

   <uses-sdk android:minSdkVersion="9" />
    
   <permission
        android:name="sohail.aziz.READ"
        android:protectionLevel="signature" />
   <permission
        android:name="sohail.aziz.WRITE"
        android:protectionLevel="signature" />

   <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >

       
        <provider android:name=".MyContentProvider"
          android:authorities="sohail.aziz.mycontentprovider"

          android:enabled="true"
          android:exported="true"
          android:readPermission="sohail.aziz.READ"
          android:writePermission="sohail.aziz.WRITE" />
    </application>

</manifest> 


We have defined two custom permissions sohail.aziz.READ and sohail.aziz.WRITE and used them as content provider's readPermission and writePermission. By defining these permissions we made it mandatory for other apps to have these permission in order to READ (Query) and WRITE (insert, update,delete) to this content provider.

Accessing Content Provider from other Apps:

In order to access this custom content provider from other apps, we need to have following things:

  •     Name of the needed permissions if any.
  •     Content URI of all the tables we need to interact.
  •     Exact fields/Cols name of the particular table.

Lets see how can we perform CRUD operations on the MyContentProvider defined here. Content URI for the categories table defined in MyContentDescriptor will be:

Uri  categoriesUri=Uri.parse("content://sohail.aziz.mycontentprovider/categories");

Lets put some content values in mycontentprovider from another app, say sample.apk.

 
public class MainActivity extends Activity {

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


Uri  categoriesURI=Uri.parse("content://sohail.aziz.mycontentprovider/categories"); 

 ContentValues vals=new ContentValues();

    /* name and status are the field name of categories table in MyContentProvider*/

            vals.put("name","sohail");
            vals.put("status",false);
            getContentResolver().insert(categoriesURI,vals);

   }
}


Same way we can call the other CRUD operations. As discussed at the start will be required to have  sohail.aziz.READ and/or sohail.aziz.WRITE permissions.

Friday, 29 March 2013

BiMap: A two-way Map in #android

A simple hashMap is used to keep key,value pairs. It is useful in situation where we need to lookup values by keys. e.g
        
HashMap<String, String> simplemap=new HashMap<String, String>();
        simplemap.put("name", "sohail");
        
        simplemap.get("name");


Above is the simple example of hashmap, where we put a key,value pair and then lookup using key. However, there are situations where we need reverse lookup i.e lookup by value. An example of such case can be a Map of the indexes of two lists, and we need to lookup one's index by other and vice versa. A map which can be looked-up by keys as well as values, is called BiMap provided by Guava.
We can use HashBiMap to create a bimap and then use its map.inverse function to get inverse map.
 
       
HashBiMap<String, String> map = HashBiMap.create();

        map.put("name", "Sohail");
        map.put("country", "Pakistan");

        Log.d("tag", "name is " + map.get("name"));
       
       
        BiMap<String, String>invmap= map.inverse();
       
        Log.d("tag", "Pakistan is a " + invmap.get("Pakistan"));


  

Wednesday, 20 March 2013

AntiSpyware Fraud on #Android's Google Play


States have been making money for decades on the name of  "Security". After the revolution of mobile phones many companies are doing the same in the world of  mobile applications. After Realizing the fact that many people, who owns smart phones, are conscious about their privacy, and majority of those do not really know about the security and privacy, are easy prey of such companies.

One such example of such apps is MPL AntiSpy by Mobile Privacy Labs.  This is $10 paid app! By placing such high price they try to give user an impression that this is REAL anti spyware. Not only this, an other app  AntiSpy TESTFILE by sample company is published and been declared a metric for AntiSpyware apps. This is what is written in the description of MPL AntiSpy

To verify MPL AntiSpy works correctly on your phone, please download AntiSpy TESTFILE for free. MPL AntiSpy will detect the TESTFILE as spyware if it works.
(Download here: http://play.google.com/store/apps/details?id=com.ANTISPY.TESTFILE)

To test MPL AntiSpy "Email Alert" feature
1. Enable Email Alert. Click "Menu"-> Type valid email address -> Exit from AntiSpy.
2. Install AntiSpy TESTFILE.
3. Check Toast note saying that "TESTFILE is SPYWARE app."
4. Check your email for Email Alert about the SPYWARE app was found.
I was getting curious to know what is ANTISPY TESTFILE is. So I de-compiled it by standard apk decompilation tools (apktool, dex2jar, jd). What I found is: this ANTISPY TESTFILE is not any way similar to the behavior and capabilities of a real spyware. It CANNOT read Contacts, SMS, Calllogs, browsing history, location etc (as shown in the screen shot of MPL AntiSpy).

Its well known fact that in android environment, Every application needs access permissions to access phone's Contacts, SMS, Calllogs etc. However ANTISPY TESTFILE doesn't request any such permissions nor do anything like real spyware.

So whats the purpose of all this? The idea is to fool "security conscious" innocent users and give impression that MPL AntiSpy is only real anti-spyware because it detects ANTISPY TESTFILE while others do not. Mobile Privacy Labs is making money by fraud with innocent android users. Lets see whether Google will protect its users from such "legal" fraud. Below are the screen shots of ANTISPY TESTFILE source code.















Tuesday, 22 January 2013

Db4o Concurrent Access

Db4o has access limitations, which means you cannot use the ObjectContainer to query/store objects in different process than the one it was opened in. For example if you open db

ObjectContainer db= Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(),
DB_PATH);

You cannot use this ObjectContainer, db for database operations in other processes. If you try to open db again in some other process you might get
DatabaseFileLockedException. So db4o don't allow concurrent access this way. However, in real world applications, we need to perform database operations in many different threads, AsyncTask and services. To perform such concurrent operations in isolation, db4o provides many mechanisms, one of which is, opening different db sessions in different processes. Once you have opened the database, You can use it to open session in different process like:

ObjectContainer db_session= db.ext.openSession();

now we can execute all db operations with this ObjectContainer e.g

  db_session.store(someObj);
  db_session.commit();

However, keep in mind, that you need to explicitly close the session by

  db_session.Close();
 
Below is a helper class, can be used to open/close db.

//////////////////////////////////////////////////////////////////////////////////////
public class dbHelper {

private static ObjectContainer database;
private static final String DATABASE_NAME = "My_database.db4o";
private static final int DATABASE_MODE = 0;
 private static Context ctx;

public dbHelper(Context context) {

   ctx=context;
   database=null;

}

private OpenDatabse(){

try {
if (database == null) {
database = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(),
db4oDBFullPath());
}
} catch (Exception ie) {
Log.e(DbHelper.class.getName(), ie.toString());
}


}

private String db4oDBFullPath() {
return ctx.getDir("data", DATABASE_MODE) + "/" + DATABASE_NAME;
}

public void close() {
if (this.database != null) {
this.database.close();
}
}

public ObjectContainer getDatabaseSession() {

         return database.ext().openSession();
}

}


/////////////////////////////////////////////////////////////////////////////////

Using this helper class we can pen the db somewhere at application start,


dbHelper helper= new dbHelper(context);
helper.OpenDatabse();

and close the db some where at application finish, by

helper.CloseDb();

Now in any process where you want to access db, use getSession method

ObjectContainer db= helper.getDatabaseSession();

db.store(someObj);
db.commit();
db.Close();

 That's it about concurrent access, hope it will be helpful.