Pages

Monday, 22 December 2014

Android Call log details after ending call using ContentObserver & PhoneStateListener

This android tutorial will cover below questions:

1. How to get incoming call number in android (Broadcast receiver for incoming number)?
2. How to get outgoing call number in android (Broadcast receiver for outgoing number)?
3. How to know call is ended or not in android?
4. How to get call log details after ending call in android?
5. How to create and use SQLite Data Base in android?
6. How to fire Content Observer after ending call?

I had worked a lot with phone state and spend time and money to know what exactly android doing. Now I am here with working code and going to share with all coders.

I am using broadcast receiver to know state of the phone and get incoming and outgoing number. Phone has three states:

1. Ringing: call is coming.
2. Idle: inactive or call disconnected.
3. Offhook: active state.

We can easily detect phone state and can know when call is ending using idle state and get new call log details but we can face any problems which are listing below:

1. Idle state call once again after idle state sometime (or in some device).
2. Idle state call in between Offhook state sometime (or in some device).
3. Call ended but call log not created yet and we queried to fetch new call log information and get wrong result sometime.

Hmm… Problems can create new problem(s). But After lot of research, finally I solved all the problems and used Content Observer to keep track on changes in call logs and use if condition to solve call end problem. Now Content Observer is creating a new problem and calling many times and giving single and exact call details multiple times. Using if condition this problem is also solved.

Incoming and Outgoing call log Details


I used simple XML layout to show call log details: your_project -> res -> layout -> activity_main.xml

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollView1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</ScrollView>

Now i used 4 Java files:-

1. MainActivity.java : To display result.

package com.example.phonehistory;

import android.support.v7.app.ActionBarActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;


public class MainActivity extends ActionBarActivity {
 
 DBHelper db;
 TextView tv;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv=(TextView)findViewById(R.id.text);
        tv.setText("");
        
        db=new DBHelper(this, "ZnSoftech.db", null, 2);
        
        Cursor c=db.getData();
        
        if(c.getCount()>0)
        {
         c.moveToFirst();
          do
            {
             String number=c.getString(0);
             String date=c.getString(1);
             String time=c.getString(2);
             String duration=c.getString(3);
             String type=c.getString(4);
             
             tv.append("Number:"+number+"\nDate:"+date+"\nTime:"+time+"\nDuration:"+duration+"\nCall Type:"+type+"\n\n");
            }while(c.moveToNext());
        }
        else
        {
         tv.setText("No Incoming and Outgoing call history exists!!!");
        }
       
    }


    @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) {
         db.deleteTable();
         tv.setText("No Incoming and Outgoing call history exists!!!");
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

2. PhListener.java : To listen phone state.

package com.example.phonehistory;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.provider.CallLog;
import android.telephony.TelephonyManager;

@SuppressLint("SimpleDateFormat") public class PhListener extends BroadcastReceiver{
 
 @Override
 public void onReceive(Context c, Intent i) {
  // TODO Auto-generated method stub
  Bundle bundle=i.getExtras();
  
  if(bundle==null)
   return;
  
  SharedPreferences sp=c.getSharedPreferences("ZnSoftech", Activity.MODE_PRIVATE);
  
  String s=bundle.getString(TelephonyManager.EXTRA_STATE);

  if(i.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL))
  {
   String number=i.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
   sp.edit().putString("number", number).commit();
   sp.edit().putString("state", s).commit();
  }
  
  else if(s.equals(TelephonyManager.EXTRA_STATE_RINGING))
  {
   String number=bundle.getString("incoming_number");
   sp.edit().putString("number", number).commit();
   sp.edit().putString("state", s).commit();
  }
  
  else if(s.equals(TelephonyManager.EXTRA_STATE_OFFHOOK))
  {
   sp.edit().putString("state", s).commit();
  }
  
  else if(s.equals(TelephonyManager.EXTRA_STATE_IDLE))
  {
   String state=sp.getString("state", null);
   if(!state.equals(TelephonyManager.EXTRA_STATE_IDLE))
   {
    sp.edit().putString("state", null).commit();
    History h=new History(new Handler(),c);
    c.getContentResolver().registerContentObserver(CallLog.Calls.CONTENT_URI, true, h);
   }
   sp.edit().putString("state", s).commit();
  }
 
 }

}

3. DBHelper.java : To store call log details in database.

package com.example.phonehistory;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper{

 public DBHelper(Context context, String name, CursorFactory factory,
   int version) {
  super(context, name, factory, version);
  // TODO Auto-generated constructor stub
 }

 @Override
 public void onCreate(SQLiteDatabase db) {
  // TODO Auto-generated method stub
  db.execSQL("create table if not exists call_history(number varchar, date varchar, time varchar, duration varchar, type varchar)");
 }

 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  // TODO Auto-generated method stub
  db.execSQL("DROP TABLE IF EXISTS call_history");
     onCreate(db);
 }
 
 public boolean insertdata(String number, String date, String time,String duration, String type)
 {
  SQLiteDatabase sdb=this.getWritableDatabase();
  sdb.execSQL("insert into call_history values('"+number+"','"+date+"','"+time+"','"+duration+"','"+type+"')");
  return true;
 }
 
 public Cursor getData()
 {
  SQLiteDatabase sdb=this.getReadableDatabase();
  Cursor c=sdb.rawQuery("select * from call_history", null);
  return c;
 }
 public void deleteTable()
 {
  SQLiteDatabase db=this.getWritableDatabase();
  db.execSQL("DROP TABLE IF EXISTS call_history");
  onCreate(db);
 }

}


4. History.java : To fire content observer to keep track on changes in call log.

package com.example.phonehistory;

import java.text.SimpleDateFormat;
import java.util.Date;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.os.Handler;
import android.provider.CallLog;

@SuppressLint("SimpleDateFormat") 
public class History extends ContentObserver {

 Context c;
 
 public History(Handler handler, Context cc) {
  // TODO Auto-generated constructor stub
  super(handler);
  c=cc;
 }
 
 @Override
    public boolean deliverSelfNotifications() {
        return true;
    }

 @Override
 public void onChange(boolean selfChange) {
  // TODO Auto-generated method stub
  super.onChange(selfChange);
  SharedPreferences sp=c.getSharedPreferences("ZnSoftech", Activity.MODE_PRIVATE);
  String number=sp.getString("number", null);
  if(number!=null)
  {
   getCalldetailsNow();
   sp.edit().putString("number", null).commit();
  }
 }
 
 private void getCalldetailsNow() {
  // TODO Auto-generated method stub
  
  Cursor managedCursor=c.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, null, null, android.provider.CallLog.Calls.DATE + " DESC");
  
  int number = managedCursor.getColumnIndex( CallLog.Calls.NUMBER ); 
        int duration1 = managedCursor.getColumnIndex( CallLog.Calls.DURATION);
        int type1=managedCursor.getColumnIndex(CallLog.Calls.TYPE);
        int date1=managedCursor.getColumnIndex(CallLog.Calls.DATE);
        
        if( managedCursor.moveToFirst() == true ) {
            String phNumber = managedCursor.getString(number);
            String callDuration = managedCursor.getString(duration1);
            
            String type=managedCursor.getString(type1);
            String date=managedCursor.getString(date1);
            
            String dir = null;
      int dircode = Integer.parseInt(type);
      switch (dircode)
      { 
      case CallLog.Calls.OUTGOING_TYPE:
       dir = "OUTGOING";
       break;
      case CallLog.Calls.INCOMING_TYPE:
       dir = "INCOMING";
       break;
      case CallLog.Calls.MISSED_TYPE:
       dir = "MISSED";
       break;
      default: 
       dir = "MISSED";
       break;
      }
      
      SimpleDateFormat sdf_date = new SimpleDateFormat("dd/MM/yyyy");
      SimpleDateFormat sdf_time = new SimpleDateFormat("h:mm a");
     // SimpleDateFormat sdf_dur = new SimpleDateFormat("KK:mm:ss");
      
      String dateString = sdf_date.format(new Date(Long.parseLong(date)));
      String timeString = sdf_time.format(new Date(Long.parseLong(date)));
    //  String duration_new=sdf_dur.format(new Date(Long.parseLong(callDuration)));

           DBHelper db=new DBHelper(c, "ZnSoftech.db", null, 2);
           db.insertdata(phNumber, dateString, timeString, callDuration, dir);
           
        }
        
        managedCursor.close();
 }

}

Now change AndroidManifast.xml file and define all necessary permissions.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.phonehistory"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />
    
 <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.phonehistory.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <receiver android:name="com.example.phonehistory.PhListener" > 
                        <intent-filter
                            android:priority="-1"> 
                            <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
                         <action android:name="android.intent.action.PHONE_STATE"/>
                        </intent-filter>  
                </receiver> 
                
    </application>

</manifest>

Now run your code and check android project. If you have any problems feel free to ask me. Share if you like this post and don’t forget to leave valuable comment regarding call log details android post or this website. Github link of this project: Android Call Log Details

Related Tutorials:

1. Access Call, Camera, Web pages
2. Use SQLite Database
3. Use Shared Preferences
4. Print numbers from 1 to 100 in ScrollView
5. Create Menu using XML

28 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. logcat Error:
    05-29 22:01:12.180 13207-13207/com.prodip.phonehistory E/AndroidRuntime﹕ FATAL EXCEPTION: main
    java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.CallLogProvider from ProcessRecord{41da19e0 13207:com.prodip.phonehistory/10153} (pid=13207, uid=10153) requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS
    at android.os.Parcel.readException(Parcel.java:1327)
    at android.os.Parcel.readException(Parcel.java:1281)
    at android.app.ActivityManagerProxy.getContentProvider(ActivityManagerNative.java:2298)
    at android.app.ActivityThread.acquireProvider(ActivityThread.java:4041)
    at android.app.ContextImpl$ApplicationContentResolver.acquireProvider(ContextImpl.java:1771)
    at android.content.ContentResolver.acquireProvider(ContentResolver.java:918)
    at android.content.ContentResolver.query(ContentResolver.java:305)
    at com.prodip.phonehistory.History.getCalldetailsNow(History.java:46)
    at com.prodip.phonehistory.History.onChange(History.java:38)
    at android.database.ContentObserver$NotificationRunnable.run(ContentObserver.java:43)
    at android.os.Handler.handleCallback(Handler.java:605)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4517)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)
    at dalvik.system.NativeStart.main(Native Method)

    ReplyDelete
  3. show following line error when i check by call by my device :
    "Cursor managedCursor=c.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, null, null, android.provider.CallLog.Calls.DATE + " DESC");"

    and unfortunately stop the app

    ReplyDelete
    Replies
    1. requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS, add those permissions in your manifest file.

      Delete
  4. showing no history exists

    ReplyDelete
  5. Not Showing any history Worst !

    ReplyDelete
  6. have some problm sir

    ReplyDelete
  7. Hi, I have a questios about broadcast receiver. How can ı dedect call when it is anwered. when i look examples ı found that listener "CALL_STATE_IDLE,CALL_STATE_OFFHOOK".
    Best Regards,
    Mehmet

    ReplyDelete
    Replies
    1. All details are given above and Call state offhook is called when user is on call.

      Delete
    2. bagaimana cara mengetahui bahwa telepon sudah terhubung pada saat pengguna menggunakan pemanggilan telepon keluar ?

      Delete
  8. Hi, how could I do to read call log only for one contact (for this contact, I know id, name and phone numbers) ?

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_GRANTED)
    error in this wrong argument found

    ReplyDelete
  11. sir when i copy this code i got error in MainActivity.java
    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;
    }

    IN THE ABOVE CODE I GOT ERROR IN .menu

    if (id == R.id.action_settings) {
    db.deleteTable();
    tv.setText("No Incoming and Outgoing call history exists!!!");
    return true;
    }

    AND IN THE ABOVE CODE I GOT ERROR IN action_settings

    ReplyDelete
  12. for me also when i run the code it shows that "NO INCOMING AND OUTGOING CALL HISTORY EXISTS"

    ReplyDelete
  13. Anonymous11:35 pm

    Had you resolved the issue ?

    ReplyDelete
  14. pada pemanggilan telepon keluar berlangsung dan klik untuk menyelesaikan panggilan. maka bagaimana caranya untuk mengetahui durasi panggilan tersebut ?

    ReplyDelete
  15. sir i have error no incoming and outgoing call exists!!!

    ReplyDelete
  16. Thanks a lot for this code! It's working correctly but I am able to detect incoming and missed calls. The outgoing calls are not getting detected. What might be the reason or solution for it?

    ReplyDelete
    Replies
    1. Anonymous3:03 pm

      is we have to call the call Intent firstly??? to detect incoming outgoing call?

      Delete
  17. Anonymous3:08 pm

    it's showing no incoming and outgoing calls..how should i get the history as you shown?

    ReplyDelete
  18. how can I get the calls logs if the app is killed or removed from the stack.?

    ReplyDelete

Back to Top