Category Archives: Android

NTLM Authentication in Android.

I was developing an Android app which uses SOAP library to call .Net web services. Web services were not accessible directly. Before calling web services user must get authenticated. Here I met with concept called Windows Authentication.

I searched on internet for  NTLM authentication in Android. I came up with using DefaultHttpClient class provided in  org.apache.http.impl.client package.

I was able to log in by using below code ,

 NtlmTransport httpTransport = new NtlmTransport();
 httpTransport.setCredentials(URL, USERNAME, PASSWORD, "","");
 SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
 envelope.dotNet = true;    
 envelope.implicitTypes = true;
 envelope.setOutputSoapObject(request);                
 httpTransport.call(PRODUCT_DETAILS_SOAP_ACTION, envelope);

But It was not possible for me to call complex web services which accept some parameters. I searched on internet but I didn’t get any solution for that.I was using android-ntlm-master library project which has NtlmTransport.java class in it.This class extends org.ksoap2.transport.Transport class.To see what happens inside I extracted this org.ksoap2.transport.Transport class from ksoap library.

This is a abstract class for both HttpTransportSE and NtlmTransport class. I did one change here in Transport class.I added one method call() which looks like this,

public void call(String targetNamespace, SoapEnvelope envelope) throws IOException, XmlPullParserException {
      this.call(targetNamespace, envelope, (List)null);
   }

Now Transport.java class looks like this:

package org.ksoap2.transport;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Proxy;
import java.util.List;
import org.ksoap2.SoapEnvelope;
import org.kxml2.io.KXmlParser;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;

public abstract class Transport {

   protected Proxy proxy;
   protected String url;
   protected int timeout;
   public boolean debug;
   public String requestDump;
   public String responseDump;
   private String xmlVersionTag;


   public Transport() {
      this.timeout = 20000;
      this.xmlVersionTag = "";
   }

   public Transport(String url) {
      this((Proxy)null, url);
   }

   public Transport(String url, int timeout) {
      this.timeout = 20000;
      this.xmlVersionTag = "";
      this.url = url;
      this.timeout = timeout;
   }

   public Transport(Proxy proxy, String url) {
      this.timeout = 20000;
      this.xmlVersionTag = "";
      this.proxy = proxy;
      this.url = url;
   }

   protected void parseResponse(SoapEnvelope envelope, InputStream is) throws XmlPullParserException, IOException {
      KXmlParser xp = new KXmlParser();
      xp.setFeature("http://xmlpull.org/v1/doc/features.html#process-namespaces", true);
      xp.setInput(is, (String)null);
      envelope.parse(xp);
   }

   protected byte[] createRequestData(SoapEnvelope envelope) throws IOException {
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      bos.write(this.xmlVersionTag.getBytes());
      KXmlSerializer xw = new KXmlSerializer();
      xw.setOutput(bos, (String)null);
      envelope.write(xw);
      xw.flush();
      bos.write(13);
      bos.write(10);
      bos.flush();
      return bos.toByteArray();
   }

   public void setUrl(String url) {
      this.url = url;
   }

   public void setXmlVersionTag(String tag) {
      this.xmlVersionTag = tag;
   }

   public void reset() {
   }

   public abstract List call(String var1, SoapEnvelope var2, List var3) throws IOException, XmlPullParserException;

   public void call(String targetNamespace, SoapEnvelope envelope) throws IOException, XmlPullParserException {
      this.call(targetNamespace, envelope, (List)null);
   }

   public abstract String getHost();

   public abstract int getPort();

   public abstract String getPath();
}
and NtlmTransport.java class:
package com.example.ksoapdemo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeFactory;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.auth.NTLMScheme;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.transport.ServiceConnection;
import org.ksoap2.transport.Transport;
import org.xmlpull.v1.XmlPullParserException;
import org.apache.http.HttpEntity;
import eu.masconsult.android_ntlm.JCIFSEngine;

public class NtlmTransport extends Transport {

    static final String ENCODING = "utf-8";

    private final DefaultHttpClient client = new DefaultHttpClient();
    private final HttpContext localContext = new BasicHttpContext();
    private String urlString;
    private String user;
    private String password;
    private String ntDomain;
    private String ntWorkstation;

    public void setCredentials(String url, String user, String password,
                               String domain, String workStation) {
        this.urlString = url;
        this.user = user;
        this.password = password;
        this.ntDomain = domain;
        this.ntWorkstation = workStation;

    }

    public List call(String targetNamespace, SoapEnvelope envelope, List headers)
            throws IOException, XmlPullParserException {
        return call(targetNamespace, envelope, headers, null);
    }

    public List call(String soapAction, SoapEnvelope envelope, List headers, File outputFile)
            throws IOException, XmlPullParserException {
        
        HttpResponse resp = null;
        try {
            //setupNtlm(urlString, user, password);  
             DefaultHttpClient httpclient = new DefaultHttpClient();
             httpclient.getAuthSchemes().register("ntlm", new NTLMSchemeFactory());
             httpclient.getCredentialsProvider().setCredentials(            
                    new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),               
                    new NTCredentials(user, password, "", "")
             );
             HttpPost httpget = new HttpPost(urlString);       
             httpget.addHeader("soapaction",  soapAction);        
             httpget.addHeader("Content-Type", "text/xml; charset=utf-8");
             byte[] requestData = null;
             try {
                 requestData = createRequestData(envelope);                 
             } catch (IOException iOException) {
             }
             ByteArrayEntity byteArrayEntity = new ByteArrayEntity(requestData);
             httpget.setEntity(byteArrayEntity);                
             resp = httpclient.execute(httpget); 

             if(resp  == null) {
                System.out.println("Response is null");
             }
             HttpEntity respEntity = resp.getEntity();

             InputStream is = respEntity.getContent();
             if(is == null) {
                System.out.println("InputStream is null");
             }
             parseResponse(envelope, is);

        } catch (Exception ex) {
            // ex.printStackTrace();
        }

        if (resp != null) {
            return Arrays.asList(resp.getAllHeaders());
        } else {
            return null;
        }
    }

    private void setHeaders(String soapAction, SoapEnvelope envelope, HttpPost httppost, List headers) {
        byte[] requestData = null;
        try {
            requestData = createRequestData(envelope);
        } catch (IOException iOException) {
        }
        ByteArrayEntity byteArrayEntity = new ByteArrayEntity(requestData);
        httppost.setEntity(byteArrayEntity);
        httppost.addHeader("User-Agent", "kSOAP/2.0");
        httppost.addHeader("SOAPAction", soapAction);
        httppost.addHeader("Content-Type", "text/xml; charset=utf-8");
        //org.ksoap2.transport.Transport.USER_AGENT);
        // SOAPAction is not a valid header for VER12 so do not add
        // it
        // @see "http://code.google.com/p/ksoap2-android/issues/detail?id=67
      /*  if (envelope.version != SoapSerializationEnvelope.VER12) {
            httppost.addHeader("SOAPAction", soapAction);
        }

        if (envelope.version == SoapSerializationEnvelope.VER12) {
            httppost.addHeader("Content-Type", Transport.CONTENT_TYPE_SOAP_XML_CHARSET_UTF_8);
        } else {
            httppost.addHeader("Content-Type", Transport.CONTENT_TYPE_XML_CHARSET_UTF_8);
        }*/

        // Pass the headers provided by the user along with the call
        if (headers != null) {
            for (int i = 0; i < headers.size(); i++) {
                HeaderProperty hp = (HeaderProperty) headers.get(i);
                httppost.addHeader(hp.getKey(), hp.getValue());
                //System.out.println(hp.getKey()+":"+hp.getValue());
            }
        }
    }

    // Try to execute a cheap method first. This will trigger NTLM authentication
    public void setupNtlm(String dummyUrl, String userId, String password) {
        try {

            ((AbstractHttpClient) client).getAuthSchemes().register("ntlm", new NTLMSchemeFactory());

            NTCredentials creds = new NTCredentials(userId, password, ntWorkstation, ntDomain);
            client.getCredentialsProvider().setCredentials(AuthScope.ANY, creds);

            HttpGet httpget = new HttpGet(dummyUrl);

            HttpResponse response1 = client.execute(httpget, localContext);
            HttpEntity entity1 = response1.getEntity();

            Header[] hArray = response1.getAllHeaders();
            int size = hArray.length;
          /*  StatusLine status = response1.getStatusLine();
            System.out.println("SERVER STATUS CODE"+status.getStatusCode());*/
            for (int i = 0; i < size; i ++) {
                Header h = hArray[i];
               //System.out.println(h.getName()+":"+h.getValue());
                if (h.getName().equals("WWW-Authenticate")) {
                    entity1.consumeContent();
                    throw new Exception("Failed Authentication");
                }
            }
           /* StatusLine status = response1.getStatusLine();
            System.out.println("SERVER STATUS CODE"+status.getStatusCode());
            String responseBody = EntityUtils.toString(entity1);
            System.out.println("================================");
            System.out.println(responseBody);
            System.out.println("================================");*/
            entity1.consumeContent();
        } catch (Exception ex) {
            // swallow
        }
    }

        //NTLM Scheme factory
    private class NTLMSchemeFactory implements AuthSchemeFactory {
        public AuthScheme newInstance(final HttpParams params) {
        // see http://www.robertkuzma.com/2011/07/
        // manipulating-sharepoint-list-items-with-android-java-and-ntlm-authentication/
            return new NTLMScheme(new JCIFSEngine());
        }
    }

    public ServiceConnection getServiceConnection() throws IOException
    {
        throw new IOException("Not using ServiceConnection in transport");
    }

    public String getHost() {
        String retVal = null;
        try {
            retVal = new URL(url).getHost();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return retVal;
    }

    public int getPort() {
        int retVal = -1;
        try {
            retVal = new URL(url).getPort();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return retVal;
    }

    public String getPath() {
        String retVal = null;
        try {
            retVal = new URL(url).getPath();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return retVal;
    }
}

To call any web service from your app one just needs to write below code:

      SoapObject request = new SoapObject(NAMESPACE, PRODUCT_DETAILS_METHOD_NAME);
      request.addProperty("ListingID", Integer.parseInt(Product_ID));
      NtlmTransport httpTransport = new NtlmTransport();
      httpTransport.setCredentials(URL, USERNAME, PASSWORD, "","");
      SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
      envelope.dotNet = true;    
      envelope.implicitTypes = true;
      envelope.setOutputSoapObject(request);                
      httpTransport.call(PRODUCT_DETAILS_SOAP_ACTION, envelope);
      SoapObject response = (SoapObject) envelope.getResponse();

I know this is not a one time authentication. Each time before calling web service one need to use this code.I need to use NtlmTransport class. But it solved my issue.

Sectioned ListView in Android

There are some examples  on internet which show how one can create sectioned list view in Android.If we open contact page in our Android phone we can see sectioned list view.When we type any letter, header get displayed at top of list view.I was designing same functionality.When user enters any word ,app should display matching header(Not sub-section within header)  at top of list.

I found code for  Sectioned List view on internet.But search functionality was something different. It was displaying subsection item at top of list.My requirement was to display section header at top of list.

I changed onTouchEvent method of IndexScroller class.Instead of directly setting particular position to the top,first search for matching header. I used LinkedHashMap here, because order of insertion is important for in this case.

I am posting code below:

This is SetupAdapter.java:

public class SetupAdapter 
{
 public static String AMERICA = "AMERICA"; 
 public static String CANADA = "CANADA"; 
 public static String INDIA ="INDIA";
 public static String RUSSIA = "RUSSIA";
 public static String UK = "UK";
 LinkedHashMap <String, String[]> adaptermap = new LinkedHashMap <String, String[]>(); 
 
 
 public SetupAdapter()
 {
 String AMERICA_States[] = 
 {
 "California","Colorado","Delaware","Florida","Georgia","Hawaii","Indiana",
 "Michigan","New York"
 };
 
 String CANADA_States[] =
 {
 "Ontario","Quebec","British Columbia","Alberta","Manitoba","Saskatchewan",
 "Nova Scotia","New Brunswick","Prince Edward Island","Northwest Territories",
 "Yukon","Nunavut"
 };
 
 String INDIA_States[] = 
 {
 "MAHARASHTRA","GUJRAT","KERALA","TAMILNADU","KARNATAK","ANDHRAPRADESH"
 };
 
 String RUSSIA_States[] = 
 {
 "Adygea","Amur","Moskau","Tatarstan","Dagestan","Stavropol","Saratov",
 "Novosibirsk","Voronez"
 };
 String UK_States[] = 
 {
 "ENGLAND","SCOTLAND","WALES"
 };
 
 adaptermap.put(AMERICA,AMERICA_States);
 adaptermap.put(CANADA,CANADA_States);
 adaptermap.put(INDIA,INDIA_States);
 adaptermap.put(RUSSIA,RUSSIA_States);
 adaptermap.put(UK,UK_States); 
 }
 
 public LinkedHashMap <String, String[]> setUpAdapterValue(Context context,SeparatedListAdapter adapter) {
 // TODO Auto-generated method stub
 
 adapter.addSection(AMERICA, new ArrayAdapter<String>(context, 
 R.layout.list_item, adaptermap.get(AMERICA))); 
 adapter.addSection(CANADA, new ArrayAdapter<String>(context, 
 R.layout.list_item, adaptermap.get(CANADA))); 
 adapter.addSection(INDIA, new ArrayAdapter<String>(context, 
 R.layout.list_item, adaptermap.get(INDIA))); 
 adapter.addSection(RUSSIA, new ArrayAdapter<String>(context, 
 R.layout.list_item, adaptermap.get(RUSSIA))); 
 adapter.addSection(UK, new ArrayAdapter<String>(context, 
 R.layout.list_item, adaptermap.get(UK))); 
 return adaptermap; 
 }
}

IndexScroller.java:

public class IndexScroller {
 
 private float mIndexbarWidth;
 private float mIndexbarMargin;
 private float mPreviewPadding;
 private float mDensity;
 private float mScaledDensity;
 private float mAlphaRate;
 private int mState = STATE_HIDDEN;
 private int mListViewWidth;
 private int mListViewHeight;
 private int mCurrentSection = -1;
 private boolean mIsIndexing = false;
 private ListView mListView = null;
 private SectionIndexer mIndexer = null;
 private String[] mSections = null;
 private RectF mIndexbarRect;
 
 private static final int STATE_HIDDEN = 0;
 private static final int STATE_SHOWING = 1;
 private static final int STATE_SHOWN = 2;
 private static final int STATE_HIDING = 3;
 
 LinkedHashMap <String, String[]> adaptermap = new LinkedHashMap <String, String[]>(); 
 
 public IndexScroller(Context context, ListView lv ,LinkedHashMap <String, String[]> map) {
 mDensity = context.getResources().getDisplayMetrics().density;
 mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
 Log.d("mDensity",mDensity+"");
 Log.d("mScaledDensity",mScaledDensity+"");
 mListView = lv;
 setAdapter(mListView.getAdapter());
 
 mIndexbarWidth = 20 * mDensity;
 mIndexbarMargin = 10 * mDensity;
 mPreviewPadding = 5 * mDensity;
 adaptermap = map;
 }

 public void draw(Canvas canvas) {
 if (mState == STATE_HIDDEN)
 return;
 Log.d("IndexScroller","draw");
 // mAlphaRate determines the rate of opacity
 Paint indexbarPaint = new Paint();
 indexbarPaint.setColor(Color.BLACK);
 indexbarPaint.setAlpha((int) (64 * mAlphaRate));
 indexbarPaint.setAntiAlias(true);
 canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity, indexbarPaint);
 
 if (mSections != null && mSections.length > 0) {
 // Preview is shown when mCurrentSection is set
 if (mCurrentSection >= 0) {
 Paint previewPaint = new Paint();
 previewPaint.setColor(Color.WHITE);
 previewPaint.setAlpha(96);
 previewPaint.setAntiAlias(true);
 previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0));
 
 Paint previewTextPaint = new Paint();
 previewTextPaint.setColor(Color.GREEN);
 previewTextPaint.setAntiAlias(true);
 previewTextPaint.setTextSize(50 * mScaledDensity);
 
 float previewTextWidth = previewTextPaint.measureText(mSections[mCurrentSection]);
 float previewSize = 2 * mPreviewPadding + previewTextPaint.descent() - previewTextPaint.ascent();
 RectF previewRect = new RectF((mListViewWidth - previewSize) / 2
 , (mListViewHeight - previewSize) / 2
 , (mListViewWidth - previewSize) / 2 + previewSize
 , (mListViewHeight - previewSize) / 2 + previewSize); 
 
 canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
 canvas.drawText(mSections[mCurrentSection], previewRect.left + (previewSize - previewTextWidth) / 2 - 1
 , previewRect.top + mPreviewPadding - previewTextPaint.ascent() + 1, previewTextPaint);
 }
 
 Paint indexPaint = new Paint();
 indexPaint.setColor(Color.BLACK);
 indexPaint.setAlpha((int) (255 * mAlphaRate));
 indexPaint.setAntiAlias(true);
 indexPaint.setTextSize(12 * mScaledDensity);
 
 float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length;
 float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint.ascent())) / 2;
 for (int i = 0; i < mSections.length; i++) {
 float paddingLeft = (mIndexbarWidth - indexPaint.measureText(mSections[i])) / 2; 
 canvas.drawText(mSections[i], mIndexbarRect.left + paddingLeft
 , mIndexbarRect.top + mIndexbarMargin + sectionHeight * i + paddingTop - indexPaint.ascent(), indexPaint);
 
 }
 }
 }
 
 
 public boolean onTouchEvent(MotionEvent ev) {
 switch (ev.getAction()) {
 case MotionEvent.ACTION_DOWN:
 // If down event occurs inside index bar region, start indexing
 if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) {
 setState(STATE_SHOWN);
 
 // It demonstrates that the motion event started from index bar
 mIsIndexing = true;
 // Determine which section the point is in, and move the list to that section
 mCurrentSection = getSectionByPoint(ev.getY()); 
 int position = setPosition(mSections[mCurrentSection]);
 //mListView.setSelection(mIndexer.getPositionForSection(mCurrentSection));
 if(position >= 0)
 {
 Log.d("ACTION_DOWN getPositionForSection",position+"");
 mListView.setSelection(position); 
 }
 return true;
 }
 break;
 case MotionEvent.ACTION_MOVE:
 if (mIsIndexing) {
 // If this event moves inside index bar
 if (contains(ev.getX(), ev.getY())) {
 // Determine which section the point is in, and move the list to that section
 mCurrentSection = getSectionByPoint(ev.getY()); 
 int position = setPosition(mSections[mCurrentSection]);
 //mListView.setSelection(mIndexer.getPositionForSection(mCurrentSection));
 if(position >= 0)
 {
 Log.d("ACTION_DOWN getPositionForSection",position+"");
 mListView.setSelection(position); 
 } 
 }
 return true;
 }
 break;
 case MotionEvent.ACTION_UP:
 if (mIsIndexing) {
 mIsIndexing = false;
 mCurrentSection = -1;
 }
 if (mState == STATE_SHOWN)
 setState(STATE_HIDING);
 break;
 }
 return false;
 }
 
 int setPosition(String letter)
 {
 Iterator<?> iterator = adaptermap.entrySet().iterator();
 int itemcount = 0;
 while(iterator.hasNext())
 {
 try
 {
 Map.Entry pairs = (Map.Entry)iterator.next();
 String key = pairs.getKey()+"";
 String[] value = (String[])pairs.getValue();
 String firstChar = key.charAt(0)+""; 
 if(firstChar.equalsIgnoreCase(letter))
 {
 Log.d(itemcount+":",key); 
 return itemcount;
 } 
 itemcount = itemcount + (value.length+1);
 }
 
 catch(Exception e) {
 e.printStackTrace();
 return -1;
 } 
 }
 return -1;
 }
 public void onSizeChanged(int w, int h, int oldw, int oldh) {
 mListViewWidth = w;
 mListViewHeight = h;
 mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth
 , mIndexbarMargin
 , w - mIndexbarMargin
 , h - mIndexbarMargin);
 }
 
 public void show() {
 if (mState == STATE_HIDDEN)
 setState(STATE_SHOWING);
 else if (mState == STATE_HIDING)
 setState(STATE_HIDING);
 }
 
 public void hide() {
 if (mState == STATE_SHOWN)
 setState(STATE_HIDING);
 }
 
 public void setAdapter(Adapter adapter) {
 if (adapter instanceof SectionIndexer) {
 mIndexer = (SectionIndexer) adapter; 
 int getPositionForSection = mIndexer.getPositionForSection(1); 
 Log.d("getPositionForSection",getPositionForSection+""); 
 mSections = (String[]) mIndexer.getSections();
 
 /*Log.d("IndexScroller","setAdapter");
 for(int i=0;i<mSections.length;i++)
 Log.d("IndexScroller",mSections[i]);*/
 }
 }
 
 private void setState(int state) {
 if (state < STATE_HIDDEN || state > STATE_HIDING)
 {
 Log.d("setState","Returning");
 return;
 }
 
 mState = state;
 switch (mState) {
 case STATE_HIDDEN:
 // Cancel any fade effect
 mHandler.removeMessages(0);
 break;
 case STATE_SHOWING:
 // Start to fade in
 mAlphaRate = 0;
 fade(0);
 break;
 case STATE_SHOWN:
 // Cancel any fade effect
 mHandler.removeMessages(0);
 break;
 case STATE_HIDING:
 // Start to fade out after three seconds
 mAlphaRate = 1;
 fade(3000);
 break;
 }
 }
 
 private boolean contains(float x, float y) {
 // Determine if the point is in index bar region, which includes the right margin of the bar
 return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top + mIndexbarRect.height());
 }
 
 private int getSectionByPoint(float y) {
 if (mSections == null || mSections.length == 0)
 return 0;
 if (y < mIndexbarRect.top + mIndexbarMargin)
 return 0;
 if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin) 
 return mSections.length - 1;
 return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length));
 }
 
 private void fade(long delay) {
 mHandler.removeMessages(0);
 mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay);
 }
 
 private Handler mHandler = new Handler() {

 @Override
 public void handleMessage(Message msg) {
 super.handleMessage(msg);
 
 switch (mState) {
 case STATE_SHOWING:
 // Fade in effect
 mAlphaRate += (1 - mAlphaRate) * 0.2;
 if (mAlphaRate > 0.9) {
 mAlphaRate = 1;
 setState(STATE_SHOWN);
 }
 
 mListView.invalidate();
 fade(10);
 break;
 case STATE_SHOWN:
 // If no action, hide automatically
 setState(STATE_HIDING);
 break;
 case STATE_HIDING:
 // Fade out effect
 mAlphaRate -= mAlphaRate * 0.2;
 if (mAlphaRate < 0.1) {
 mAlphaRate = 0;
 setState(STATE_HIDDEN);
 }
 
 mListView.invalidate();
 fade(10);
 break;
 }
 }
 
 };
}

MainActivity.java:

public class MainActivity extends Activity {
 
 IndexableListView indexableList;
 private ArrayList<String> mItems;
 LinkedHashMap <String, String[]> adaptermap = new LinkedHashMap <String, String[]>(); 
 //LinearLayout linearlayout;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); 
 //setContentView(R.layout.activity_main); 
 //linearlayout = (LinearLayout)findViewById(R.id.linearlayout);
 SeparatedListAdapter adapter = new SeparatedListAdapter(this); 
 SetupAdapter setupadapter = new SetupAdapter();
 adaptermap = setupadapter.setUpAdapterValue(this,adapter); 
 indexableList = new IndexableListView(this,adaptermap);
 indexableList.setAdapter(adapter); 
 indexableList.setFastScrollEnabled(true);
 //indexableList.setPadding(0,0,50,0 );
 //linearlayout.addView(indexableList);
 this.setContentView(indexableList); 
 } 

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 // Inflate the menu; this adds items to the action bar if it is present.
 getMenuInflater().inflate(R.menu.main, menu);
 return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
 // Handle action bar item clicks here. The action bar will
 // automatically handle clicks on the Home/Up button, so long
 // as you specify a parent activity in AndroidManifest.xml.
 int id = item.getItemId();
 if (id == R.id.action_settings) {
 return true;
 }
 return super.onOptionsItemSelected(item);
 }
 
 public class SeparatedListAdapter extends BaseAdapter implements SectionIndexer {
 
 public final Map<String,Adapter> sections = new LinkedHashMap<String,Adapter>();
 public final ArrayAdapter<String> headers;
 public final static int TYPE_SECTION_HEADER = 0;
 
 private String mSections = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
 @Override
 public int getPositionForSection(int section) {
 // If there is no item for current section, previous section will be selected
 for (int i = section; i >= 0; i--) {
 for (int j = 0; j < getCount(); j++) {
 if (i == 0) {
 // For numeric section
 for (int k = 0; k <= 9; k++) {
 if (StringMatcher.match(String.valueOf((getItem(j)+"").charAt(0)), String.valueOf(k)))
 return j;
 }
 } else {
 if (StringMatcher.match(String.valueOf((getItem(j)+"").charAt(0)), String.valueOf(mSections.charAt(i))))
 return j;
 }
 }
 }
 return 0;
 }
 
 @Override
 public int getSectionForPosition(int position) {
 return 0;
 }

 @Override
 public Object[] getSections() {
 String[] sections = new String[mSections.length()];
 for (int i = 0; i < mSections.length(); i++)
 sections[i] = String.valueOf(mSections.charAt(i));
 return sections;
 }
 
 public SeparatedListAdapter(Context context) {
 headers = new ArrayAdapter<String>(context, R.layout.list_header);
 } 
 public void addSection(String section, Adapter adapter) {
 this.headers.add(section);
 this.sections.put(section, adapter);
 }
 
 public Object getItem(int position) {
 for(Object section : this.sections.keySet()) {
 Adapter adapter = sections.get(section);
 int size = adapter.getCount() + 1;
 
 // check if position inside this section 
 if(position == 0) return section;
 if(position < size) return adapter.getItem(position - 1);

 // otherwise jump into next section
 position -= size;
 }
 return null;
 }

 public int getCount() {
 // total together all sections, plus one for each section header
 int total = 0;
 for(Adapter adapter : this.sections.values())
 total += adapter.getCount() + 1;
 return total;
 }

 public int getViewTypeCount() {
 // assume that headers count as one, then total all sections
 int total = 1;
 for(Adapter adapter : this.sections.values())
 total += adapter.getViewTypeCount();
 return total;
 }
 
 public int getItemViewType(int position) {
 int type = 1;
 for(Object section : this.sections.keySet()) {
 Adapter adapter = sections.get(section);
 int size = adapter.getCount() + 1;
 
 // check if position inside this section 
 if(position == 0) return TYPE_SECTION_HEADER;
 if(position < size) return type + adapter.getItemViewType(position - 1);

 // otherwise jump into next section
 position -= size;
 type += adapter.getViewTypeCount();
 }
 return -1;
 }
 
 public boolean areAllItemsSelectable() {
 return false;
 }

 public boolean isEnabled(int position) {
 return (getItemViewType(position) != TYPE_SECTION_HEADER);
 }
 
 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
 int sectionnum = 0;
 for(Object section : this.sections.keySet()) {
 Adapter adapter = sections.get(section);
 int size = adapter.getCount() + 1;
 
 // check if position inside this section 
 if(position == 0) {
 TextView view = (TextView)headers.getView(sectionnum, convertView, parent);
 view.setTextColor(Color.parseColor("#FFFFFF"));
 view.setBackgroundColor(Color.parseColor("#000000"));
 return view;
 }
 if(position < size) {
 TextView view = (TextView)adapter.getView(position - 1, convertView, parent);
 return view;
 }

 //otherwise jump into next section
 position -= size;
 sectionnum++;
 }
 return null;
 }

 @Override
 public long getItemId(int position) {
 return position;
 }
 } 
}

IndexableListView.java:

public class IndexableListView extends ListView {
 
 private boolean mIsFastScrollEnabled = false;
 private IndexScroller mScroller = null;
 private GestureDetector mGestureDetector = null;
 LinkedHashMap <String, String[]> adaptermap = new LinkedHashMap <String, String[]>(); 
 
 public IndexableListView(Context context,LinkedHashMap <String, String[]> map) {
 super(context);
 adaptermap = map;
 }
 public IndexableListView(Context context, AttributeSet attrs) {
 super(context, attrs);
 }

 public IndexableListView(Context context, AttributeSet attrs, int defStyle) {
 super(context, attrs, defStyle);
 }

 @Override
 public boolean isFastScrollEnabled() {
 return mIsFastScrollEnabled;
 }

 @Override
 public void setFastScrollEnabled(boolean enabled) {
 mIsFastScrollEnabled = enabled;
 if (mIsFastScrollEnabled) {
 if (mScroller == null)
 mScroller = new IndexScroller(getContext(), this,adaptermap);
 } else {
 if (mScroller != null) {
 mScroller.hide();
 mScroller = null;
 }
 }
 }

 @Override
 public void draw(Canvas canvas) {
 super.draw(canvas);
 Log.d("IndexableListView ","draw");
 // Overlay index bar
 if (mScroller != null) {
 mScroller.draw(canvas);
 /**
 * I added mScroller.show() method here to show Index scroller automatically.
 * Index scroller will open as soon as activity starts.
 */
 mScroller.show();
 } 
 }

 @Override
 public boolean onTouchEvent(MotionEvent ev) {
 // Intercept ListView's touch event
 if (mScroller != null && mScroller.onTouchEvent(ev))
 return true; 
 if (mGestureDetector == null) {
 mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {

 @Override
 public boolean onFling(MotionEvent e1, MotionEvent e2,
 float velocityX, float velocityY) {
 // If fling happens, index bar shows
 mScroller.show();
 return super.onFling(e1, e2, velocityX, velocityY);
 }
 
 /**
 * I am adding this 2 methods here.
 * onScroll
 * onSingleTapConfirmed
 * Index scroller will become visible.
 */
 
 @Override
 public boolean onScroll(MotionEvent e1, MotionEvent e2,
 float distanceX, float distanceY) {
 // TODO Auto-generated method stub
 mScroller.show();
 return super.onScroll(e1, e2, distanceX, distanceY);
 }
 
 @Override
 public boolean onSingleTapConfirmed(MotionEvent e) {
 // TODO Auto-generated method stub
 mScroller.show();
 return super.onSingleTapConfirmed(e);
 } 
 });
 }
 mGestureDetector.onTouchEvent(ev); 
 return super.onTouchEvent(ev);
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
 return true;
 }

 @Override
 public void setAdapter(ListAdapter adapter) {
 super.setAdapter(adapter);
 if (mScroller != null)
 mScroller.setAdapter(adapter);
 }

 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 super.onSizeChanged(w, h, oldw, oldh);
 if (mScroller != null)
 mScroller.onSizeChanged(w, h, oldw, oldh);
 }
}

Enable or Disable ActionBar Tab in Android

Recently , I faced one problem in my Android app. App has been designed to show 3 tabs.On first tab user enters credentials that are necessary for log in.On second tab he will select some category.On third tab app will show brief information of his selected category. User should go to next tab only after filling  all details on first tab.Navigation between tabs should happen through next and previous buttons.

Key requirements were:

  1. User should not go to to next tab by swiping of tab – App should block tab swiping.
  2. User should not go to next tab by manually selecting particular tab –  App should block manual selection of tab.

I managed to solve first problem easily by creating Custom ViewPager class and adding it into layout xml file.

But second requirement  was tricky. I searched internet but I found that there is no enable/disable functionality for ActionBar tabs in Android .In first answer here and in second answer here they suggested to remove tab. But my requirement was different.Though I managed to prevent tab from loading data but still I was unable to stop it from going to next tab.

I come up with solution of saving position of tab and using boolean value to determine from where tab selection has requested.If user touch the button within tab then app will save position and flag for that. If user manually tries to select tab by pressing it, boolean value will be false.

When user touch Next/Previous button app send BroadCast request to Activity. Inside activity BroadcastReceiver will handle this request. I used DataSetter class to set and get value of position and boolean flag.

Code for 3 Tab fragments:

This is FirstFragment.java :

public class FirstFragment extends Fragment 
{
 Button next;
 
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) 
 {
 View rootView = inflater.inflate(R.layout.first_tab_fragment, container, false); 
 next = (Button)rootView.findViewById(R.id.nextbutton);
 next.setOnClickListener(new OnClickListener() {
 
 @Override
 public void onClick(View v) {
 // TODO Auto-generated method stub
 
 Intent intent = new Intent("TAB_CLICKED");
 Bundle bundle = new Bundle();
/*
 * I am manually adding position here.
 * Add appropriate tab position here.
 */
 bundle.putInt(MainActivity.POSITION,1);
 intent.putExtras(bundle);
 getActivity().sendBroadcast(intent);
 }
 });
 return rootView;
 }
}

SecondFragment.java:

public class SecondFragment extends Fragment 
{
  Button previous,next; 
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) 
 {
 Log.i("onCreateView","Loading the fragment"); 
 View rootView = inflater.inflate(R.layout.second_tab_fragment, container, false); 
 previous = (Button)rootView.findViewById(R.id.gameprevious);
 next = (Button)rootView.findViewById(R.id.gamenext);
 previous.setOnClickListener(new OnClickListener() 
 {
 @Override
 public void onClick(View v) 
 {
 // TODO Auto-generated method stub
 Intent intent = new Intent("TAB_CLICKED");
 Bundle bundle = new Bundle();
/*
 * I am manually adding position here.
 * Add appropriate tab position here.
 */
 bundle.putInt(MainActivity.POSITION,0);
 
 intent.putExtras(bundle);
 getActivity().sendBroadcast(intent); 
 }
 });
 
 next.setOnClickListener(new OnClickListener()
 {
 @Override
 public void onClick(View v)
 {
 // TODO Auto-generated method stub
 Intent intent = new Intent("TAB_CLICKED");
 Bundle bundle = new Bundle();
/*
 * I am manually adding position here.
 * Add appropriate tab position here.
 */
 bundle.putInt(MainActivity.POSITION,2);
 intent.putExtras(bundle);
 getActivity().sendBroadcast(intent); 
 }
 }); 
 return rootView;
 }
 
 @Override
 public void onResume() {
 super.onResume();
 };
 
 
 @Override
 public void onAttach(android.app.Activity activity) {
 super.onAttach(activity); 
 };
 
 @Override
 public void onActivityCreated(Bundle savedInstanceState) {
 super.onActivityCreated(savedInstanceState); 
 };
 
 @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 };
 
}

ThirdFragment.java:

public class ThirdFragment extends Fragment { 
 Button previous;
 
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) { 
 View rootView = inflater.inflate(R.layout.third_tab_fragment, container, false);
 previous = (Button)rootView.findViewById(R.id.previousbutton);
 
 previous.setOnClickListener(new OnClickListener() 
 {
 @Override
 public void onClick(View v) 
 {
 // TODO Auto-generated method stub
 Intent intent = new Intent("TAB_CLICKED");
 Bundle bundle = new Bundle();
/*
 * I am manually adding position here.
 * Add appropriate tab position here.
 */
 bundle.putInt(MainActivity.POSITION,1);
 intent.putExtras(bundle);
 getActivity().sendBroadcast(intent);
 }
 }); 
 return rootView;
 } 
}

MainActivity.java:

public class MainActivity extends FragmentActivity implements
ActionBar.TabListener
{
 CustomViewPager viewPager;
 TabsPagerAdapter mAdapter;
 ActionBar mActionBar;
 String[] tabs = { "First Tab", "Second Tab", "Third Tab" };
 public static final String POSITION = "POSITION";
 public static final String USERCLICK = "USERCLICK";
 DataSetter setter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) 
 {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 setter = new DataSetter(); 
 IntentFilter filter = new IntentFilter("TAB_CLICKED");
 this.registerReceiver(receiver, filter);
 
 int actionBarTitleId = Resources.getSystem().getIdentifier(
 "action_bar_title", "id", "android");
 if (actionBarTitleId > 0) {
 TextView title = (TextView) findViewById(actionBarTitleId);
 if (title != null) {
 title.setTextColor(Color.parseColor("#1d497d"));
 }
 }
 mActionBar = getActionBar();
 mActionBar.setDisplayHomeAsUpEnabled(true);
 mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
 | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
 mActionBar.setTitle(R.string.app_name);
 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
 mActionBar.setCustomView(R.layout.customactionbar);
 viewPager = (CustomViewPager) findViewById(R.id.pager);
 viewPager.setPagingEnabled(false);
 mActionBar = getActionBar();
 mActionBar.setDisplayHomeAsUpEnabled(true);
 mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
 | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM); 
 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
 mAdapter = new TabsPagerAdapter(getSupportFragmentManager()); 
 viewPager.setAdapter(mAdapter); 
 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 
 // Adding Tabs
 for (String tab_name : tabs) 
 {
 mActionBar.addTab(mActionBar.newTab().setText(tab_name)
 .setTabListener(this));
 } 
 viewPager.setOnClickListener(new OnClickListener() {
 
 @Override
 public void onClick(View view) {
 // TODO Auto-generated method stub
 Log.d("View",view.getClass().getName()); 
 }
 }); 
 ViewPager.SimpleOnPageChangeListener ViewPagerListner = new ViewPager.SimpleOnPageChangeListener() {
 @Override
 public void onPageSelected(int position) {
 // TODO Auto-generated method stub
 super.onPageSelected(position); 
 mActionBar.setSelectedNavigationItem(position);
 }
 }; 
 viewPager.setOnPageChangeListener(ViewPagerListner); 
 } 
 
 @Override
 protected void onDestroy() {
 // TODO Auto-generated method stub
 super.onDestroy();
 unregisterReceiver(receiver);
 }
 
 @Override
 public boolean onCreateOptionsMenu(Menu menu) 
 {
 // Inflate the menu; this adds items to the action bar if it is present.
 getMenuInflater().inflate(R.menu.main, menu);
 return true; 
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) 
 {
 // TODO Auto-generated method stub
 super.onOptionsItemSelected(item);
 return true; 
 }

 @Override
 public void onTabReselected(Tab tab, FragmentTransaction ft) {
 // TODO Auto-generated method stub 
 
 }
 
 @Override
 public void onTabSelected(Tab tab, FragmentTransaction ft) {
 
 Log.d("MainActivity","onTabSelected"); 
 if(setter.getFlag() == true)
 {
 viewPager.setCurrentItem(setter.getposition());
 } 
 }

 @Override
 public void onTabUnselected(Tab tab, FragmentTransaction ft) {
 // TODO Auto-generated method stub
 Log.d("MainActivity","onTabUnselected"); 
 //handler.sendEmptyMessage(0);
 Message message = new Message();
 message.arg1 = setter.getposition();
 handler.sendMessage(message); 
 }
 
 Handler handler = new Handler()
 {
 public void handleMessage(android.os.Message msg)
 {
 int arg1 = msg.arg1; 
 Log.d("arg1",arg1+"");
 //viewPager.setCurrentItem(arg1);
 mActionBar.setSelectedNavigationItem(arg1);
 Log.d("handler","Inside handler");
 }
 };
 
 BroadcastReceiver receiver = new BroadcastReceiver(){ 
 public void onReceive(Context context, android.content.Intent intent) {
 
 String action = intent.getAction();
 Bundle bundle = intent.getExtras();
 int position = bundle.getInt(MainActivity.POSITION);
 Log.d("Action",action);
 Log.d("position",position+"");
 setter.setposition(position);
 setter.setflag(true);
 Message message = new Message();
 message.arg1 = position;
 handler.sendMessage(message);
 };
 };
}

CustomViewPager.java:

public class CustomViewPager extends ViewPager { 

 private boolean enabled;
 public CustomViewPager(Context context) {
 super(context);
 }

 public CustomViewPager(Context context, AttributeSet attrs) {
 super(context, attrs);
 }
 
 @Override
 public boolean onTouchEvent(MotionEvent event) {
 // TODO Auto-generated method stub
 if (this.enabled) {
 return super.onTouchEvent(event);
 }
 return false;
 }
 
 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
 // TODO Auto-generated method stub
 if (this.enabled) {
 return super.onInterceptTouchEvent(event);
 }
 return false;
 }
 
 public void setPagingEnabled(boolean enabled) {
 this.enabled = enabled;
 } 
}

activity_main.xml:

<com.example.tabswithswipe.CustomViewPager 
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/pager"
 android:layout_width="match_parent"
 android:layout_height="match_parent"/>

DataSetter.java:

public class DataSetter {
 
 int position;
 boolean flag; 
 void setposition(int value)
 {
 position = value;
 } 
 void setflag(boolean value)
 {
 flag = value;
 } 
 int getposition()
 {
 return position;
 } 
 boolean getFlag()
 {
 return flag;
 }
}