Wednesday, October 17, 2018

How to get frame rate (FPS) and number of frames programmatically from a video in Android

17th of October, 2018.

Yesterday I was assigned to get metadata (width, height, frame rate and no of frames)  of a video recorded by camera. I have gone through many articles and stackoverflow answers, but none of them have fully covered what I want. After playing with android framework I manged to complete my assignment. I will show you what I  have learned.

What I need was either number of frames or frame rate. If I have one then I can calculate other because I have the video length.
 First I tried to use MediaPlayer class. There were 2 promising methods in this class.

 val mediaPlayer: MediaPlayer = MediaPlayer.create(context, uri)  
 val frames = mediaPlayer.metrics.get(MediaPlayer.MetricsConstants.FRAMES)  

However this method requires api level 28. My minimum api level was 21 so this was not going to work. There was also another method call,

 val frameRate = mediaPlayer.syncParams.frameRate  

However this always returns null for me. I wasn't eager to chase this around because I had another promising class which named MediaMetadataRetriever.

 val retriever = MediaMetadataRetriever()  
 val frameRate = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE)  

However this method also requires android api level 23 and also it returns me null.

Finally I found MediaExtractor class which is compatible with android api level 16 and gives me what I want.

 private fun getVideoMetaData(uri: Uri) : VideoMetaData{  
     val mediaExtractor = MediaExtractor()  
     mediaExtractor.setDataSource(uri.path)  
     val format = mediaExtractor.getTrackFormat(0)  
     mediaExtractor.release()  
     return if(format.containsKey(MediaFormat.KEY_FRAME_RATE)){  
       val frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE)  
       val frames = frameRate * (format.getLong(MediaFormat.KEY_DURATION)/ 1000000)  
       VideoMetaData(format.getInteger(MediaFormat.KEY_WIDTH).toString(),  
           format.getInteger(MediaFormat.KEY_HEIGHT).toString(),  
           frames.toString(),  
           frameRate.toString())  
     }else {  
       VideoMetaData(format.getInteger(MediaFormat.KEY_WIDTH).toString(), format.getInteger(MediaFormat.KEY_HEIGHT).toString())  
     }  
   }  

** VideoMetaData is a simple data class

So that's it. Hope this will be a help for someone.
:-)





Monday, December 11, 2017

Android ViewPager detect swipe beyond bounds and go to next activity.

Date : 11 / 12 / 2017

Today my client wants me to automatically open the next view(next activity) when the user comes to the last page and swipe left. I have searched and found good solutions on SO but they seems to be complex than I thought. So I looked and played with some methods in ViewPager and created an easy solution.

I used  OnPageChangeListener in  ViewPager  class. Here we have 3 methods.


  1. void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
  2. void onPageSelected(int position);
  3. void onPageScrollStateChanged(int state);
We are going to use onPageSelected() and onPageScrollStateChanged() for our purpose.

It is important to know what are the states in  onPageScrollStateChanged() and their sequence of execution.There are 3 states as SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING and SCROLL_STATE_SETTLING.

This is extracted from android documentation.
/** * Called when the scroll state changes. Useful for discovering when the user 
* begins dragging, when the pager is automatically settling to the current page, 
* or when it is fully stopped/idle. 
* * @param state The new scroll state. 
* @see ViewPager#SCROLL_STATE_IDLE 
* @see ViewPager#SCROLL_STATE_DRAGGING 
* @see ViewPager#SCROLL_STATE_SETTLING */
void onPageScrollStateChanged(int state);

Here is the execution order.
Scroll_state_dragging --> Scroll_state_settling --> onPageSelected() --> Scroll_state_idle

(note: scroll_state_dragging occur multiple times.)

The idea is keeping a flag inside onPageSelected() to record current page number. Then if user in the last page and swipe left scroll_state_dragging called and launch the next view.

private int pagePosition; // keep a class variable
private int[] layouts= new int[]{
        R.layout.welcome_slide1,
        R.layout.welcome_slide2,
        R.layout.welcome_slide3}; 
ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() {

    @Override   
    public void onPageSelected(int position) {
        addBottomDots(position);
        pagePosition = position;
    }

    @Override 
    public void onPageScrolled(int position, float positionOffset, int arg2) {
    }

    @Override   
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_DRAGGING) {
            if (pagePosition == layouts.length - 1) {
                launchHomeScreen();
            }
        }
    }
};
private void launchHomeScreen() {
    startActivity(new Intent(IntroductionActivity.this, MainDockerActivity.class));
    finish();
}

Easy as that. Here is my full code implementation. (filter for what you want.)

 package com.YOURAPP.android.app;  
 import android.content.Context;  
 import android.content.Intent;  
 import android.graphics.Color;  
 import android.graphics.Typeface;  
 import android.os.Build;  
 import android.os.Bundle;  
 import android.support.v4.content.res.ResourcesCompat;  
 import android.support.v4.view.PagerAdapter;  
 import android.support.v4.view.ViewPager;  
 import android.text.Html;  
 import android.view.LayoutInflater;  
 import android.view.View;  
 import android.view.ViewGroup;  
 import android.view.Window;  
 import android.view.WindowManager;  
 import android.widget.Button;  
 import android.widget.LinearLayout;  
 import android.widget.TextView;  
 import butterknife.BindView;  
 public class IntroductionActivity extends BaseActivity {  
   @BindView(R.id.view_pager_intro)  
   ViewPager viewPager;  
   @BindView(R.id.btn_skip)  
   Button btnSkip;  
   private LinearLayout dotsLayout;  
   private int[] layouts;  
   private Typeface typefaceNormal;  
   private Typeface typefaceBold;  
   private int pagePosition;  
   @Override  
   protected void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     if (Build.VERSION.SDK_INT >= 21) {  
       getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);  
     }  
     dotsLayout = findViewById(R.id.layoutDots);  
     layouts = new int[]{  
         R.layout.welcome_slide1,  
         R.layout.welcome_slide2,  
         R.layout.welcome_slide3};  
     typefaceNormal = ResourcesCompat.getFont(this, R.font.gill_sans_book);  
     typefaceBold = ResourcesCompat.getFont(this, R.font.gill_sans_bold);  
     addBottomDots(0);  
     // making notification bar transparent  
     changeStatusBarColor();  
     MyViewPagerAdapter myViewPagerAdapter = new MyViewPagerAdapter();  
     viewPager.setAdapter(myViewPagerAdapter);  
     viewPager.addOnPageChangeListener(viewPagerPageChangeListener);  
     btnSkip.setOnClickListener(new View.OnClickListener() {  
       @Override  
       public void onClick(View v) {  
         launchHomeScreen();  
       }  
     });  
   }  
   @Override  
   protected int getContentView() {  
     return R.layout.activity_introduction;  
   }  
   private void addBottomDots(int currentPage) {  
     TextView[] dots = new TextView[layouts.length];  
     int[] colorsActive = getResources().getIntArray(R.array.array_dot_active);  
     int[] colorsInactive = getResources().getIntArray(R.array.array_dot_inactive);  
     dotsLayout.removeAllViews();  
     for (int i = 0; i < dots.length; i++) {  
       dots[i] = new TextView(this);  
       dots[i].setText(Html.fromHtml("&#8226;"));  
       dots[i].setTextSize(35);  
       dots[i].setTextColor(colorsInactive[currentPage]);  
       dotsLayout.addView(dots[i]);  
     }  
     if (dots.length > 0)  
       dots[currentPage].setTextColor(colorsActive[currentPage]);  
   }  
   private int getItem(int i) {  
     return viewPager.getCurrentItem() + i;  
   }  
   private void launchHomeScreen() {  
     startActivity(new Intent(IntroductionActivity.this, MainDockerActivity.class));  
     finish();  
   }  
   ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() {  
     @Override  
     public void onPageSelected(int position) {  
       addBottomDots(position);  
       pagePosition = position;  
     }  
     @Override  
     public void onPageScrolled(int position, float positionOffset, int arg2) {  
     }  
     @Override  
     public void onPageScrollStateChanged(int state) {  
       if (state == ViewPager.SCROLL_STATE_DRAGGING) {  
         if (pagePosition == layouts.length - 1) {  
           launchHomeScreen();  
         }  
       }  
     }  
   };  
   //Making notification bar transparent  
   private void changeStatusBarColor() {  
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  
       Window window = getWindow();  
       window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);  
       window.setStatusBarColor(Color.TRANSPARENT);  
     }  
   }  
   public class MyViewPagerAdapter extends PagerAdapter {  
     private LayoutInflater layoutInflater;  
     public MyViewPagerAdapter() {  
     }  
     @Override  
     public Object instantiateItem(ViewGroup container, int position) {  
       layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
       View view = layoutInflater.inflate(layouts[position], container, false);  
       TextView mainTitle = view.findViewById(R.id.main_title);  
       TextView introDescription = view.findViewById(R.id.intro_description);  
       mainTitle.setTypeface(typefaceBold);  
       introDescription.setTypeface(typefaceNormal);  
       container.addView(view);  
       return view;  
     }  
     @Override  
     public int getCount() {  
       return layouts.length;  
     }  
     @Override  
     public boolean isViewFromObject(View view, Object obj) {  
       return view.equals(obj);  
     }  
     @Override  
     public void destroyItem(ViewGroup container, int position, Object object) {  
       View view = (View) object;  
       container.removeView(view);  
     }  
   }  
 }  

Hope It will be useful to someone. Happy Coding :-)

Wednesday, February 24, 2016

Common template for any android project (Multi screens support)

OutDated : Do not use this! There are better ways which android OS provides.

Hi All,

As I learn more about android, I have a need to create a common android project so  that I can use it to start the future projects which is surly easy my life. Any android project needs to support both phone and tablets for a flexible UI. So we need fragments (as google recommends) and code logic to handle multiple device support.

I'm going to show you my common project to begin for any android project which supports phones and tablets. I created this project referring developer site for android. But here I think my example will easier to read and understand. And yes, I'm still learning android so if you see any issue or things to improve, please comment. I'll really appreciate it.

Here's how my common template look like in phone and tablet.


Let's see the codes. Here's the project structure.


I put comments in the code so it should be self-explanatory.

1. AndroidManifest.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
   package="com.sajithkumara.flexibletemplate" >  
   <application  
     android:allowBackup="true"  
     android:icon="@mipmap/ic_launcher"  
     android:label="@string/app_name"  
     android:supportsRtl="true"  
     android:theme="@style/AppTheme" >  
     <activity android:name=".activity.MainActivity" >  
       <intent-filter>  
         <action android:name="android.intent.action.MAIN" />  
         <category android:name="android.intent.category.LAUNCHER" />  
       </intent-filter>  
     </activity>  
     <activity android:name=".activity.BMIActivity"></activity>  
     <activity android:name=".activity.BMRActivity"></activity>  
   </application>  
 </manifest>  

2.build.gradle (Module: app)
 apply plugin: 'com.android.application'  
 android {  
   compileSdkVersion 22  
   buildToolsVersion "22.0.1"  
   defaultConfig {  
     applicationId "com.sajithkumara.flexibletemplate"  
     minSdkVersion 16  
     targetSdkVersion 22  
     versionCode 1  
     versionName "1.0"  
   }  
   buildTypes {  
     release {  
       minifyEnabled false  
       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
     }  
   }  
 }  
 dependencies {  
   compile fileTree(dir: 'libs', include: ['*.jar'])  
   testCompile 'junit:junit:4.12'  
   compile 'com.android.support:appcompat-v7:22.2.1'  
   compile 'com.android.support:cardview-v7:22.2.1'  
 }  

3. MainActivity
 package com.sajithkumara.flexibletemplate.activity;  
 import android.content.Intent;  
 import android.support.v4.app.Fragment;  
 import android.support.v4.app.FragmentTransaction;  
 import android.os.Bundle;  
 import android.support.v7.app.AppCompatActivity;  
 import android.widget.FrameLayout;  
 import com.sajithkumara.flexibletemplate.R;  
 import com.sajithkumara.flexibletemplate.fragment.BMIFragment;  
 import com.sajithkumara.flexibletemplate.fragment.BMRFragment;  
 import com.sajithkumara.flexibletemplate.fragment.MenuFragment;  
 // this is the first view which includes MenuFragment  
 public class MainActivity extends AppCompatActivity implements MenuFragment.OnMenuItemSelectedListener {  
   FrameLayout frameContainer;  
   @Override  
   protected void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_main); // there are 2 activity_main.xml files.  
                         // 1 for phones(in layout), a for tablets (in layout-sw600dp)  
     frameContainer = (FrameLayout) findViewById(R.id.frame_container);  
     if (frameContainer == null) {  
       // If we have a phone activity_main.xml in layout folder loads.Do nothing here.  
     } else {  
       // If we have a tablet activity_main.xml in layout-sw600dp folder loads.  
       // load the BMIFragment in the frame_container  
       BMIFragment sef = new BMIFragment();  
       changeFragment(sef); // replace the fragments  
     }  
   }  
   @Override  
   public void onMenuItemSelect(int layoutId) { // Listener interface in MenuFragment  
     Intent intent = null;  
     if (frameContainer == null) { // If we have a phone.......  
       switch (layoutId) { // layoutId pass from MenuFragment  
         case 1: // if layoutId = 1, load BMIActivity(not BMIFragment)  
           intent = new Intent(this, BMIActivity.class);  
           intent.putExtra("layoutId", layoutId);  
           startActivity(intent);  
           return;  
         case 2:// if layoutId = 2, load BMRActivity(not BMRFragment)  
           intent = new Intent(this, BMRActivity.class);  
           startActivity(intent);  
           return;  
       }  
     } else {  // If we have a tablet.......  
       switch (layoutId) {  
         case 1: //load BMIFragment  
           BMIFragment sef = new BMIFragment();  
           changeFragment(sef);  
           return;  
         case 2: //load BMRFragment  
           BMRFragment bmiFragment = new BMRFragment();  
           changeFragment(bmiFragment);  
           return;  
       }  
     }  
   }  
   private void changeFragment(Fragment fragment) {  
     FragmentTransaction fragmentTransaction = null;  
     fragmentTransaction = getSupportFragmentManager().beginTransaction();  
     fragmentTransaction.replace(R.id.frame_container, fragment);  
     fragmentTransaction.commit();  
   }  
 }  

4. BMIActivity
 package com.sajithkumara.flexibletemplate.activity;  
 import android.os.Bundle;  
 import android.support.v7.app.AppCompatActivity;  
 import android.view.MenuItem;  
 import com.sajithkumara.flexibletemplate.R;  
 public class BMIActivity extends AppCompatActivity {  
   // Put all the java controlling code in BMIFragment. No need to code here.  
   @Override  
   protected void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_bmi);  
   }  
   @Override  
   public boolean onOptionsItemSelected(MenuItem item) {  
     this.finish(); // end this activity and go back  
     return super.onOptionsItemSelected(item);  
   }  
 }  

5. BMRActivity
 package com.sajithkumara.flexibletemplate.activity;  
 import android.os.Bundle;  
 import android.support.v7.app.AppCompatActivity;  
 import android.view.MenuItem;  
 import com.sajithkumara.flexibletemplate.R;  
 public class BMRActivity extends AppCompatActivity {  
   // Put all the java controlling code in BMRFragment. No need to code here.  
   @Override  
   protected void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_bmr);  
   }  
   @Override  
   public boolean onOptionsItemSelected(MenuItem item) {  
     this.finish(); // end this activity and go back  
     return super.onOptionsItemSelected(item);  
   }  
 }  

6. MenuFragment
 package com.sajithkumara.flexibletemplate.fragment;  
 import android.app.Activity;  
 import android.os.Bundle;  
 import android.support.v4.app.Fragment;  
 import android.support.v7.widget.CardView;  
 import android.view.LayoutInflater;  
 import android.view.View;  
 import android.view.ViewGroup;  
 import com.sajithkumara.flexibletemplate.R;  
 public class MenuFragment extends Fragment {  
   private OnMenuItemSelectedListener mListener;  
   CardView cardViewBMI, cardViewBMR;  
   public static MenuFragment newInstance(String param1, String param2) {  
     MenuFragment fragment = new MenuFragment();  
     return fragment;  
   }  
   public MenuFragment() {  
     // Required empty public constructor  
   }  
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
   }  
   @Override  
   public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                Bundle savedInstanceState) {  
     View v = inflater.inflate(R.layout.fragment_menu, container, false);  
     cardViewBMI = (CardView) v.findViewById(R.id.cardBMI);  
     cardViewBMR = (CardView) v.findViewById(R.id.cardBMR);  
     cardViewBMI.setOnClickListener(new View.OnClickListener() {  
       @Override  
       public void onClick(View v) {  
         onMenuItemSelected(1); // layoutId = 1 to identify BMI card view click  
       }  
     });  
     cardViewBMR.setOnClickListener(new View.OnClickListener() {  
       @Override  
       public void onClick(View v) {  
         onMenuItemSelected(2); // layoutId = 2 to identify BMR card view click  
       }  
     });  
     return v;  
   }  
   public void onMenuItemSelected(int layoutId) {  
     if (mListener != null) {  
       mListener.onMenuItemSelect(layoutId);  
     }  
   }  
   @Override  
   public void onAttach(Activity activity) {  
     super.onAttach(activity);  
     try {  
       mListener = (OnMenuItemSelectedListener) activity;  
     } catch (ClassCastException e) {  
       throw new ClassCastException(activity.toString()  
           + " must implement OnMenuItemSelectedListener");  
     }  
   }  
   @Override  
   public void onDetach() {  
     super.onDetach();  
     mListener = null;  
   }  
   public interface OnMenuItemSelectedListener {  
     public void onMenuItemSelect(int layoutId);  
   }  
 }  

7. BMIFragment
 package com.sajithkumara.flexibletemplate.fragment;  
 import android.os.Bundle;  
 import android.support.v4.app.Fragment;  
 import android.support.v7.app.ActionBar;  
 import android.support.v7.app.AppCompatActivity;  
 import android.view.LayoutInflater;  
 import android.view.View;  
 import android.view.ViewGroup;  
 import android.widget.Button;  
 import android.widget.Toast;  
 import com.sajithkumara.flexibletemplate.R;  
 public class BMIFragment extends Fragment {  
   Button btnToast;  
   public BMIFragment() {  
     // Required empty public constructor  
   }  
   @Override  
   public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                Bundle savedInstanceState) {  
     // Inflate the layout for this fragment  
     View v = inflater.inflate(R.layout.fragment_bmi, container, false);  
     //adding action bar and setting backward navigation  
     ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();  
     if (getActivity().getClass().getSimpleName().equals("BMIActivity")) {  
       actionBar.setDisplayHomeAsUpEnabled(true);  
     }  
     actionBar.setTitle("BMI Calculator");  
     btnToast = (Button)v.findViewById(R.id.btnToast);  
     btnToast.setOnClickListener(new View.OnClickListener() {  
       @Override  
       public void onClick(View v) {  
         Toast.makeText(getActivity(),"You clicked toast message button!!!",Toast.LENGTH_SHORT).show();  
       }  
     });  
     return v;  
   }  
 }  

8. BMRFragment
 package com.sajithkumara.flexibletemplate.fragment;  
 import android.os.Bundle;  
 import android.support.v4.app.Fragment;  
 import android.support.v7.app.ActionBar;  
 import android.support.v7.app.AppCompatActivity;  
 import android.view.LayoutInflater;  
 import android.view.View;  
 import android.view.ViewGroup;  
 import com.sajithkumara.flexibletemplate.R;  
 public class BMRFragment extends Fragment {  
   public BMRFragment() {  
     // Required empty public constructor  
   }  
   @Override  
   public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                Bundle savedInstanceState) {  
     // Inflate the layout for this fragment  
     View v = inflater.inflate(R.layout.fragment_bmr, container, false);  
     //adding action bar and setting backward navigation  
     ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();  
     if (getActivity().getClass().getSimpleName().equals("BMRActivity")) {  
       actionBar.setDisplayHomeAsUpEnabled(true);  
     }  
     actionBar.setTitle("BMR Calculator");  
     return v;  
   }  
 }  

9. activity_main.xml (in layout folder)
 <?xml version="1.0" encoding="utf-8"?>  
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"  
   android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"  
   android:paddingRight="@dimen/activity_horizontal_margin"  
   android:paddingTop="@dimen/activity_vertical_margin"  
   android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">  
   <fragment  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:name="com.sajithkumara.flexibletemplate.fragment.MenuFragment"  
     android:id="@+id/menuFragInActivity"  
     tools:layout="@layout/fragment_menu" />  
 </RelativeLayout>  

10. activity_main.xml (in layout-sw600dp folder)
 <?xml version="1.0" encoding="utf-8"?>  
 <LinearLayout  
   xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context=".MainActivity">  
   <fragment  
     android:layout_width="0dp"  
     android:layout_weight="1"  
     android:layout_height="match_parent"  
     android:name="com.sajithkumara.flexibletemplate.fragment.MenuFragment"  
     android:id="@+id/menu_fragment"  
     tools:layout="@layout/fragment_menu" />  
   <FrameLayout  
     android:id="@+id/frame_container"  
     android:layout_width="0dp"  
     android:layout_weight="2"  
     android:layout_height="match_parent">  
   </FrameLayout>  
 </LinearLayout>  

11. activity_bmi.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context="com.sajithkumara.flexibletemplate.activity.BMIActivity">  
   <fragment  
     android:id="@+id/exersiceFrag"  
     android:name="com.sajithkumara.flexibletemplate.fragment.BMIFragment"  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     tools:layout="@layout/fragment_bmi" />  
   <!-- Note the layout is fragment_bmi-->  
 </RelativeLayout>  

12. activity_bmr.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <RelativeLayout  
   xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context="com.sajithkumara.flexibletemplate.activity.BMRActivity">  
   <fragment  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:name="com.sajithkumara.flexibletemplate.fragment.BMRFragment"  
     android:id="@+id/exersiceFrag"  
     tools:layout="@layout/fragment_bmr"/>  
   <!-- Note the layout is fragment_bmr-->  
 </RelativeLayout>  

13. fragment_bmi.xml
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   android:background="#95a5a6"  
   tools:context="com.sajithkumara.flexibletemplate.fragment.BMIFragment">  
   <TextView  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:gravity="center"  
     android:text="Here place the content for BMI calculator"  
     android:textColor="#fff"  
     android:textSize="24sp" />  
   <Button  
     android:id="@+id/btnToast"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:text="Toast Message"  
     android:layout_gravity="center_horizontal"  
     android:layout_marginTop="60dp"/>  
 </FrameLayout>  

14. fragment_bmr.xml
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context="com.sajithkumara.flexibletemplate.fragment.BMRFragment"  
   android:background="#7f8c8d">  
   <TextView  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:gravity="center"  
     android:text="BMR Calculator goes here.Place the content for BMR calculator"  
     android:textColor="#fff"  
     android:textSize="24sp" />  
 </FrameLayout>  

15. fragment_menu.xml
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:app="http://schemas.android.com/apk/res-auto"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context="com.sajithkumara.flexibletemplate.fragment.MenuFragment">  
   <LinearLayout  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:orientation="vertical">  
     <android.support.v7.widget.CardView  
       android:id="@+id/cardBMI"  
       android:layout_width="match_parent"  
       android:layout_height="35dp"  
       android:layout_margin="8dp"  
       android:background="@android:color/white"  
       app:cardCornerRadius="4dp">  
       <TextView  
         android:layout_width="wrap_content"  
         android:layout_height="wrap_content"  
         android:layout_gravity="center"  
         android:text="Body Mass Index"  
         android:textSize="30sp" />  
     </android.support.v7.widget.CardView>  
     <android.support.v7.widget.CardView  
       android:id="@+id/cardBMR"  
       android:layout_width="match_parent"  
       android:layout_height="35dp"  
       android:layout_margin="8dp"  
       android:background="@android:color/white"  
       app:cardCornerRadius="4dp">  
       <TextView  
         android:layout_width="wrap_content"  
         android:layout_height="wrap_content"  
         android:layout_gravity="center"  
         android:text="Basal Metabolic Rate"  
         android:textSize="30sp" />  
     </android.support.v7.widget.CardView>  
   </LinearLayout>  
 </RelativeLayout>  

You can download from github from https://github.com/SajithKumara/AndroidFlexibleUI.git
Here is a project in playstore which used this template - BMI Calculator .


That's all. Hope it will help to someone. Happy Coding :-) ......

Monday, November 23, 2015

Getting camera captured image in android WebView

Hi all,

Last week was very exciting because I had to get an image from camera and place it inside a WebView. I have googled and couple of results were found as follows.

Here it is said that there is an issue using  "<input type="file" />" in KitKat. At the end of this link there are some links claiming as working modules for lollipop(I did't try them.) Because of this lack of useful codes I decided to share my findings which may help someone. I have tested this code in my nexus 4 running on lollipop. I have added comments in the code to understand the code. Please note that this is only for getting image from camera NOT from file chooser.

 1. AndroidManifest.xml

 <?xml version="1.0" encoding="utf-8"?>  
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
   package="com.duo.wishesapptest" >  
   <uses-permission android:name="android.permission.CAMERA" />  
   <uses-feature android:name="android.hardware.camera" />  
   <uses-permission android:name="android.permission.INTERNET" />  
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  
   <application  
     android:allowBackup="true"  
     android:icon="@mipmap/ic_launcher"  
     android:label="@string/app_name"  
     android:supportsRtl="true"  
     android:theme="@style/AppTheme" >  
     <activity  
       android:name=".CameraActivity"  
       android:label="@string/title_activity_camera"  
       android:theme="@style/AppTheme.NoActionBar" >  
       <intent-filter>  
         <action android:name="android.intent.action.MAIN" />  
         <category android:name="android.intent.category.LAUNCHER" />  
       </intent-filter>  
     </activity>  
     <activity android:name=".WebContent" >  
     </activity>  
   </application>  
 </manifest>  

2. CameraActivity.java

 package com.duo.wishesapptest;  
 import android.content.Intent;  
 import android.os.Bundle;  
 import android.support.v7.app.AppCompatActivity;  
 import android.support.v7.widget.Toolbar;  
 import android.view.View;  
 public class CameraActivity extends AppCompatActivity {  
   @Override  
   protected void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_camera);  
     Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);  
     setSupportActionBar(toolbar);  
   }  
   public void callWebview(View view){  
     Intent intent=new Intent(this,WebContent.class);  
     startActivity(intent);  
   }  
 }  

3. WebContent.java

 package com.duo.wishesapptest;  
 import android.app.Activity;  
 import android.content.Intent;  
 import android.graphics.Bitmap;  
 import android.graphics.BitmapFactory;  
 import android.net.Uri;  
 import android.os.Bundle;  
 import android.os.Environment;  
 import android.provider.MediaStore;  
 import android.support.v7.app.ActionBarActivity;  
 import android.util.Base64;  
 import android.webkit.JavascriptInterface;  
 import android.webkit.WebView;  
 import android.webkit.WebViewClient;  
 import java.io.ByteArrayOutputStream;  
 import java.io.File;  
 public class WebContent extends ActionBarActivity {  
   WebView webView;  
   static Uri picUri = null;  
   String url;  
   String uriInString;  
   boolean isFirstLoad = true; // for the first time there is no image.Used this not to show image in the first page load.  
   @Override  
   protected void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_web_content);  
     webView = (WebView) findViewById(R.id.webViewHtm);  
     webView.setWebViewClient(new WebViewClient(){  
       @Override  
       public void onPageFinished(WebView view, String url1) { // This is the important part. Everytime camera intent finish it calls onCreate method.  
         super.onPageFinished(view, url1);          // So after getting the camera image javascript run to set image in WebView  
         if(!isFirstLoad)  
         webView.loadUrl("javascript:getImage('" + url + "')");  
       }  
     });  
     webView.addJavascriptInterface(this, "Android"); // javascript interfaces are in 'this' activity  
                              // if javascript interfaces are added to another class we can't call 'startActivityForResult'  
     webView.loadUrl("https://f073b6f1598031631202dc1cf1408f54a9fede00-www.googledrive.com/host/0B_1iFNsKSU55SkdmbTlwMUQxTWc");  
             // here I published my html file in google drive.this link should work till 2016 may.(google is going to stop this service in 2016)  
             // I believe dropbox is also good to host a simple html page if you don't have a server  
     webView.getSettings().setJavaScriptEnabled(true);  
     webView.getSettings().setDomStorageEnabled(true);  
   }  
   @JavascriptInterface  
   public void showAndroidCamera() { // This is called when click the button in html page.  
     // Create AndroidExampleFolder in storage. You can see this folder by opening Android Device Monitor. See for 'mnt' folder in DDMS --> File Explorer view.  
     File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "AndroidExampleFolder");  
     if (!imageStorageDir.exists()) {  
       imageStorageDir.mkdirs(); // Create AndroidExampleFolder if not exists  
     }  
     // Create camera captured image file path and name  
     File file = new File(imageStorageDir + File.separator + "IMG"+ String.valueOf(System.currentTimeMillis())+ ".jpg");  
     Intent cameraIntent = new Intent("android.media.action.IMAGE_CAPTURE"); // crating the camera intent  
     cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));  
     picUri = Uri.fromFile(file);  
     startActivityForResult(cameraIntent, 123); // result is passed to onActivityResult  
   }  
   @Override  
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
     super.onActivityResult(requestCode, resultCode, data);  
     isFirstLoad = false;  
     uriInString = picUri.toString().replace("file://", ""); //needs to remove 'file://' part in uri path.  
     switch (requestCode) {  
       case 123:  
         if (resultCode == Activity.RESULT_OK) {  
           Bitmap bm = BitmapFactory.decodeFile(uriInString);  
           ByteArrayOutputStream baos = new ByteArrayOutputStream();  
           bm.compress(Bitmap.CompressFormat.PNG, 100, baos); //bm is the bitmap object  
           byte[] byteArrayImage = baos.toByteArray();  
           String encodedImage = Base64.encodeToString(byteArrayImage, Base64.DEFAULT);  
           url="data:image/png;base64,"+encodedImage; // encoded value passed to javascript  
         }  
     }  
   }  
 }  

4. activity_camera.xml

 <?xml version="1.0" encoding="utf-8"?>  
 <android.support.design.widget.CoordinatorLayout  
   xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:app="http://schemas.android.com/apk/res-auto"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"   
   android:fitsSystemWindows="true"  
   tools:context="com.duo.wishesapptest.CameraActivity">  
   <android.support.design.widget.AppBarLayout   
     android:layout_height="wrap_content"  
     android:layout_width="match_parent"   
     android:theme="@style/AppTheme.AppBarOverlay">  
     <android.support.v7.widget.Toolbar   
       android:id="@+id/toolbar"  
       android:layout_width="match_parent"   
       android:layout_height="?attr/actionBarSize"  
       android:background="?attr/colorPrimary"   
       app:popupTheme="@style/AppTheme.PopupOverlay" />  
   </android.support.design.widget.AppBarLayout>  
   <include layout="@layout/content_camera" />  
   <android.support.design.widget.FloatingActionButton   
     android:id="@+id/fab"  
     android:layout_width="wrap_content" android:layout_height="wrap_content"  
     android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin"  
     android:src="@android:drawable/ic_dialog_email" />  
 </android.support.design.widget.CoordinatorLayout>  

5. activity_web_content.xml

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context="com.example.lakshan.vers.WebContent">  
   <WebView  
     android:layout_width="match_parent"  
     android:layout_height="300dp"  
     android:id="@+id/webViewHtm"  
     />  
 </RelativeLayout>  

6. content_camera.xml

 <?xml version="1.0" encoding="utf-8"?>  
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   xmlns:app="http://schemas.android.com/apk/res-auto"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"  
   android:paddingRight="@dimen/activity_horizontal_margin"  
   android:paddingTop="@dimen/activity_vertical_margin"  
   android:paddingBottom="@dimen/activity_vertical_margin"  
   tools:context="com.duo.wishesapptest.CameraActivity">  
   <Button  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:text="Get Cam on web view"  
     android:id="@+id/camera_btn"  
     android:layout_alignParentTop="true"  
     android:layout_marginTop="69dp"  
     android:onClick="callWebview"/>  
 </RelativeLayout>  

7. camera.html

 <!DOCTYPE html>  
 <html>  
 <head>  
 <script type="text/javascript">  
   function showAndroidCameraHere() {  
     Android.showAndroidCamera();  
   }       
 function getImage(src) {  
   var img = document.createElement("IMG");  
      img.setAttribute("src", src);  
   img.setAttribute("width", "94");  
   img.setAttribute("height", "70");  
   img.setAttribute("alt", "Your image here");  
   document.getElementById('imageHolder').appendChild(img);  
 }  
 </script>  
 </head>  
 <body>  
 <form id="formContainer"">  
   <h2>capture=camera</h2>  
      <button type="button" onClick="showAndroidCameraHere()">Try it</button>  
      <div id="imageHolder" width="100px" height="80px"></div>  
 </form>  
 </body>  
 </html>  

8. build.gradle

 apply plugin: 'com.android.application'  
 android {  
   compileSdkVersion 22  
   buildToolsVersion "22.0.1"  
   defaultConfig {  
     applicationId "com.duo.wishesapptest"  
     minSdkVersion 19  
     targetSdkVersion 22  
     versionCode 1  
     versionName "1.0"  
   }  
   buildTypes {  
     release {  
       minifyEnabled false  
       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
     }  
   }  
 }  
 dependencies {  
   compile fileTree(dir: 'libs', include: ['*.jar'])  
   testCompile 'junit:junit:4.12'  
   compile 'com.android.support:appcompat-v7:22.2.1'  
   compile 'com.android.support:design:22.2.1'  
 }  

That's it. Hope someone can find this useful. Happy Coding :-).

Friday, September 18, 2015

How to get date from DatePickerDialog to a fragment

Recently I had a need to use a DatePickerDialog. Of course there are many posts on this topic and many stack overflow questions. But the problem was most of them are using Activity to call to a DialogFragment which create a new DatePickerDialog. What i need was a fragment to call a DialogFragment and get the selected date back to the calling fragment. I spend several hours on this topic and found some interesting things which i would like to share. (I'm using the minimum code for simplicity)

First I created a MainActivity which will hold the fragment.

1. MainActivity.java
 public class MainActivity extends Activity {  
   @Override  
   protected void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_main);  
   }  
 }  


Here is the layout of MainActivity.(activity_main.xml)

 <RelativeLayout  
   xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context=".MainActivity">  
   <fragment  
     android:id="@+id/childFragment"  
     android:name="com.primesoft.layouttestingapp.BlankFragment"  
     android:layout_height="fill_parent"  
     android:layout_width="match_parent" />  
 </RelativeLayout>  

Then I created a fragment which will call DatePickerFragment. From now on I used comments to explain the code.

2. BlankFragment.java
 public class BlankFragment extends Fragment implements DatePickerDialog.OnDateSetListener{ // implementing OnDateSetListener  
                                           // This will listen for a date set event  
   ImageButton selectDate;  
   TextView dateToActivate;  
   public BlankFragment() {  
     // Required empty public constructor  
   }  
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
   }  
   @Override  
   public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                Bundle savedInstanceState) {  
     View view;  
     view = inflater.inflate(R.layout.fragment_blank, container, false); // inflating the layout  
     selectDate = (ImageButton)view.findViewById(R.id.SelectDate); // getting the image button in fragment_blank.xml  
     dateToActivate = (TextView) view.findViewById(R.id.selectedDate); // getting the TextView in fragment_blank.xml  
     selectDate.setOnClickListener(new View.OnClickListener() {  // setting listener for user click event  
       @Override  
       public void onClick(View v) {  
         DialogFragment newFragment = new DatePickerFragment(); // creating DialogFragment which creates DatePickerDialog  
         newFragment.setTargetFragment(BlankFragment.this,0);  // Passing this fragment DatePickerFragment.  
         // As i figured out this is the best way to keep the reference to calling activity when using FRAGMENT.  
         newFragment.show(getActivity().getFragmentManager(), "datePicker");  
       }  
     });  
     return view;  
   }  
   @Override  
   public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { // what should be done when a date is selected  
     StringBuilder sb = new StringBuilder().append(dayOfMonth).append("/").append(monthOfYear + 1);  
     String formattedDate = sb.toString();  
     dateToActivate.setText(formattedDate);  
   }  
 }  
Here is the layout for BlankFragment. (fragment_blank.xml)
 <RelativeLayout  
   xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context="com.primesoft.layouttestingapp.BlankFragment"  
   android:background="@android:color/darker_gray"  
   >  
   <ImageButton  
     android:id="@+id/SelectDate"  
     android:layout_marginTop="10dp"  
     android:layout_marginLeft="10dp"  
     android:layout_width="100dp"  
     android:layout_height="100dp"  
     android:scaleType="fitCenter"  
     android:src="@drawable/date_picker" /> <!--Use your own image here-->     
    <TextView  
     android:id="@+id/selectedDate"  
     android:layout_toRightOf="@id/SelectDate"  
     android:layout_width="120dp"  
     android:layout_height="40dp"  
     android:layout_marginLeft="10dp"  
     android:layout_marginTop="35dp"/>  
 </RelativeLayout>  
And finally here is the DialogFragment which creates DatePickerDialog.

3. DatePickerFragment.java
 public class DatePickerFragment extends DialogFragment{  
   private DatePickerDialog.OnDateSetListener dateSetListener; // listener object to get calling fragment listener  
   DatePickerDialog myDatePicker;  
   @Override  
   public Dialog onCreateDialog(Bundle savedInstanceState) {  
     // Use the current date as the default date in the picker  
     final Calendar c = Calendar.getInstance();  
     int year = c.get(Calendar.YEAR);  
     int month = c.get(Calendar.MONTH);  
     int day = c.get(Calendar.DAY_OF_MONTH);  
     dateSetListener = (DatePickerDialog.OnDateSetListener)getTargetFragment(); // getting passed fragment  
     myDatePicker = new DatePickerDialog(getActivity(), dateSetListener, year, month, day); // DatePickerDialog gets callBack listener as 2nd parameter  
     // Create a new instance of DatePickerDialog and return it  
     return myDatePicker;  
   }  
 }  

Well that's it. I have attached some screen captures here. Hope this helps someone. :-)


Friday, August 28, 2015

The Evaluation of Android API levels

In the search of knowledge in android, I had this worrying question on my mind for a while. That is what are the significant highlights in android API levels and how could they impact to my development. So I search through the internet and found valuable information on android site. But it takes some time to read all these changes so I thought I need to summarize those changes so one can get a bird view on this.(Actually I have listed what I think as important. So I didn’t listed all the changes. Go to this link and you will find all the changes.)


Platform version
API Level
New Additions
Android 1.0
1
Begin of a new era
2
•      For T-Mobile G1
3
Cupcake
•      SlidingDrawer / HorizontalScrollView
•      OnScreen soft keyboard
•      Speech recognition via RecognizerIntent
•      LocationManager - Applications can get location change updates via Intent
•      WebView - Touch start/end/move/cancel DOM event support
•      Redesigned Sensor Manager APIs
•      GLSurfaceView - convenience framework for creating OpenGL  applications
•      Broadcast Intent for app update install succeeded - for smoother app  upgrade experience

4
Donut
•      New android.view.animation
•      Quick Search Box
•      New gesture API
•      New android.speech.tts
•      New SmsManager
•      New DisplayMetrics
•      New <supports-screens>
•      New <uses-feature> 
•      Battery usage indicator
•      updated OpenCore 2 media engine
5
Eclair
•      ANT support
•      New APIs for sync adapters
•      Bluetooth Support
•      Live Wallpapers
8
Froyo
•      android:installLocation install app in external storage
•      New android:backupAgent
•      generalized backup service on Cloud
•      New OpenGL ES 2.0
•      Dalvik JIT compiler
•      Portable hotspot
•      V8 JavaScript engine
•      New UI modes "car mode" and "night mode" 
•      New ScaleGestureDetector
•      The layout attribute fill_parent is renamed to match_parent
•      android.permission.SET_TIME — allows an application to set the system time.

9
Gingerbread
•      SIP based VOIP android.net.sip (Internet Calling)
•      NFC (Near Field Communication)
•      gyroscope, rotation vector, linear acceleration, gravity, and barometer
•      Multiple cameras support by Camera API 
•      New DownloadManager system service
•      New  system facility StrictMode
•      Support for overscroll
•      Text selection controls
•      Notification text and icon styles(New TextAppearance.StatusBar.EventContent, TextAppearance.StatusBar.EventContent.Title,TextAppearance.StatusBar.Icon, and TextAppearance.StatusBar.Title)
•      supports extra-large screen sizes
•      New AlarmClock
•      New  Criteria class
•      New  StorageManager
10
Gingerbread


•      NFC upgrades
11
Honeycomb
•      Introducing Fragments
•      An application will crash if it does not call through to the super implementation of its Activity.onPause() method.
•      Action bar instead of tittle bar
•      Support for multi core architecture
•      Loader: A framework component that facilitates asynchronous loading of data in combination with UI components to dynamically load data without blocking the main thread. See the Loaders developer guide
•      Drag and drop functionality
•      HTTP live streaming
•      support for Media/Picture Transfer Protocol (MTP/PTP) over USB
•      System clipboard
•      JsonReader and JsonWriter
•      Media sync from SD card
12
Honeycomb
•      Exposes an API to its built-in RTP (Real-time Transport Protocol) stack
•      homescreen widgets resizeable
•      New  LruCache class
•      USB APIs
•      MTP/PTP APIs

13
Honeycomb
•      New resource qualifiers(sw320dp, sw720dp, w720dp, h1024dp)
•      tabs are typically presented in the UI using the new ActionBar.newTab()
14
Ice Cream Sandwitch
•      Social APIs in contacts provider
•      New user profile
•      Contact usage feedback
•      New calendar API to change data which are stored in Calendar Provider
•      New Voicemail Provider
•      Camera class now includes APIs for detecting faces and controlling focus and metering areas
•      Android Beam
•      Wi-Fi peer-to-peer (P2P) connections
•      Explore-by-touch mode
•      New  VpnService
•      New  view group GridLayout
•      New  view TextureView
•      Navigation bar
•      Updated V8 JavaScript compiler
15
Ice Cream Sandwitch
•      social stream API in contacts provider
16
Jelly bean
•      Isolated services android:isolatedProcess="true" 
•      New getMyMemoryState(ActivityManager.RunningAppProcessInfo)
•      New intent protocol to directly launch the live wallpaper
•      App stack navigation using android:parentActivityName
•      The AudioEffect class now supports additional audio pre-processing types when capturing audio
•      Android Beam™ now supports large payload transfers over Bluetooth
•      The new InputManager class allows you to query the set of input devices
•      Extends vsync timing across all drawing and animation done by the Android framework
•      Expandable Notification
•      Lights out and full screen mode
17
Jelly bean
•      Daydream 
•      Allows users to add app widgets to the lock screen
•      Multiple user spaces on shareable devices such as tablets
•      Right-to-left (RTL) UIs and reading direction, such as Arabic and Hebrew
•      Nested fragments. Nested fragments are only supported when added to a fragment dynamically
•      Renderscript Compute which is the first computation platform ported to run directly on a mobile device GPU
•      Wireless display solution that complies with the Miracast
•      HDR camera scene mode

18
Jelly bean
•      Behavioral changes( load an intent in a different user profile)
•      Java interfaces and native support for OpenGL ES 3.0.
•      Mipmapping for drawables.
•      ViewOverlay class
•      Optical background layouts for 9 patch images.
•      New rotationAnimation field in WindowManager while screen rotation
•      New service class, NotificationListenerService 
•      The Trace class
•      Android sandbox reinforced with SELinux
•      Keystore provider and APIs that allow applications to create exclusive-use keys

19
KitKat
•      WebView support for new HTML5 features, and support for remote debugging
•      New android.print framework
•      The Telephony content provider (the "SMS Provider") allows apps to read and write SMS and MMS messages on the device
•      Host card emulation (emulates NFC cards for data exchange)
•      New NFC reader mode
•      Rate the current track from the remote controller
•      New android.transition framework facilitate animations between different states of your user interface
•      Built-in step sensors
•      System bars partially translucent with new themes, Theme.Holo.NoActionBar.TranslucentDecorand Theme.Holo.Light.NoActionBar.TranslucentDecor.
•      In WebView, apps targeting earlier versions will have JS URLs evaluated directly and any result of the evaluation will not replace the current page content. Apps targetting KITKAT or later that load a JS URL will have the result of that URL replace the content of the current page
•      New API, ActivityManager.isLowRamDevice(), lets you tune your app's behavior to match the device's memory configuration.
•      A new storage access framework
•      Full screen immersive mode
•      A new tool called procstats helps you analyze the memory resources your app uses
Android 4.4W

•      For Android Wear(Android watch)
21
Lollipop
•      Material Design
•      Screen capturing and screen sharing capabilities with android.media.projection APIs
•      Java interfaces and native support for OpenGL ES 3.1.
•      New android.hardware.camera2 API to facilitate fine-grain photo capture and image processing
•      Act as a Bluetooth LE peripheral device
•      improvements in battery life.(project volta)
•      new JobScheduler API that lets you optimize battery life by defining jobs for the system to run asynchronously at a later time or under specified conditions (such as when the device is charging)
•      New screen pinning API
•      Render PDF document pages into bitmap images for printing by using the new PdfRenderer class
•      New ART runtime to support a mix of ahead-of-time (AOT), just-in-time (JIT), and interpreted code
•      platform support for 64-bit architectures(Nexus 9's NVIDIA Tegra K1)
•      New  tilt detector and a heart rate sensor
22
Lollipop
•      Multiple SIM card support
Android 6.0
23
M
•      App linking
•      Automatic full data backup and restore for apps.
•      Authenticate users by using fingerprint scans on supported devices
•      Support for the Hotspot 2.0 Release 1 spec on Nexus 6 and Nexus 9 devices





So This only a small summary on Android API levels. Hope this may help to someone. :-)