Advertisment

Android Programming Tutorial: Transferring Call Logs to Calendars in Android

author-image
PCQ Bureau
New Update

As smartphones continue doing more to make our daily lives easier and more connected, the dreamers in us want to do more. And when we use a modern operating system like Android, connected does not just mean being connected with each other, it has also started to mean connecting different parts of our lives with each other. It is interesting to note how the different uses we put our smartphones to actually can be integrated better. In this article I am describing an application I wrote for a requirement I had. I use a Samsung Galaxy S3 that runs the Jelly Bean version of Android (4.1.2). If you already know Android programming reasonably, I hope you will be able to get some inputs on how to program Android better. If you are not comfortable with Android programming but are a programmer for some other platform you should be able to understand my code and carry about developing this particular application if you have the same requirement, as long as you are using an Android device of a reasonably higher version.

Advertisment

The Requirement

I use my calendar not just to plan for the future, but also as a record of the past. I make entries for the future on the basis of what I plan to do, and when the time has passed, I record what I did against the plan. It would be nice if after I finish a call the entry from the call log could be transferred to the calendar, using the start time and the end time of the call from the call log. I noticed there where some applications available in the marketplace, both free and paid, to transfer entries from the call log to the calendar. However, I did not want the transfer to happen for all calls from the call log; I want to restrict the transfer only to a set of specific people from my contacts. I could not find any application that allowed filtering for specific contacts while doing the transfer, so I decided to write an application of my own.

The Solution

If you are relatively new to Android programming, I suggest you try out the application described in the article "Solving the Contacts ‘Bug' on Android" in PC Quest March 2013 issue. In this article I am using a stepwise approach; you can run the application in the interim and observe partial results. That would be helpful in understanding one concept at a time as well as it would be easier for you to debug if you encounter an issue.

We start by creating a project in Eclipse. Accept the default values as steps towards creating the project. A ‘Hello World' text view comes by default for the layout of the default activity. Let us delete the text view as we would have our own layout.

Advertisment

In the default layout, let us have a list view as follows. I am using the list view to cross check if we are able to filter the call log the way we want before we copy the filtered call log to the calendar.

< 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=".CallLog2CalendarActivity" >

< ListView

android:id="@+id/lstCallLog"

android:layout_width="match_parent"

android:layout_height="wrap_content">

< /ListView>

< /RelativeLayout>

Let us create another layout having a text view as follows. The list view above will use the text view below to display one entry from the call log.

Advertisment

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

< LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical" >

< TextView

android:id="@+id/txtCallLog"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

< /LinearLayout>

Because we need to read the call log, we need to declare the permissions required for the application in the application manifest.

Now write the following code inside onCreate to read the call log and display in our list view. We are picking up the phone number field from the call log to be displayed in the list view:

Advertisment

ListView lstCallLog;

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_call_log2_calendar);

lstCallLog = (ListView)findViewById(R.id.lstCallLog);

String<> strFrom = new String<> { CallLog.Calls.NUMBER };

int<> intTo = new int<> { R.id.txtCallLog };

Cursor curCallLog = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, null, null, null);

SimpleCursorAdapter adpCallLog = new SimpleCursorAdapter(this, R.layout.callloglist, curCallLog, strFrom,

intTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

lstCallLog.setAdapter(adpCallLog);

You should be able to see the list of numbers from your call log when you run the application at this stage.

Next we want the name of the person contacted from the call log if the same is available in the contacts database. The layout that the ListView picks up should be revised by adding another text view as follows.

Advertisment

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

< LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical" >

< TextView

android:id="@+id/txtCallLog"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

< TextView

android:id="@+id/txtName"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

< /LinearLayout>

This is the revised code for onCreate:

ListView lstCallLog;

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_call_log2_calendar);

lstCallLog = (ListView)findViewById(R.id.lstCallLog);

String<> strFrom = new String<> { CallLog.Calls.NUMBER, CallLog.Calls.CACHED_NAME };

int<> intTo = new int<> { R.id.txtCallLog, R.id.txtName };

Cursor curCallLog = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, null, null, null);

SimpleCursorAdapter adpCallLog = new SimpleCursorAdapter(this, R.layout.callloglist, curCallLog, strFrom,

intTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

lstCallLog.setAdapter(adpCallLog);

Advertisment

You should now be able to see contact names also in the call log representation in the list view if the same is available, if you run the application at this stage.

Let us now filter the call log to display only those entries where a corresponding name is available from contacts. The following is the revised code for onCreate, where we have now included a WHERE clause:

ListView lstCallLog;

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_call_log2_calendar);

lstCallLog = (ListView)findViewById(R.id.lstCallLog);

String<> strFrom = new String<> { CallLog.Calls.NUMBER, CallLog.Calls.CACHED_NAME };

int<> intTo = new int<> { R.id.txtCallLog, R.id.txtName };

String strWhere = CallLog.Calls.CACHED_NAME + " IS NOT NULL";

Cursor curCallLog = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, strWhere, null, null);

SimpleCursorAdapter adpCallLog = new SimpleCursorAdapter(this, R.layout.callloglist, curCallLog, strFrom,

intTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

lstCallLog.setAdapter(adpCallLog);

Advertisment

If you run the application now, you should see the list further filtered as follows. As a test, we want to see if we can read from the calendar also. We need to declare the required permissions in the application manifest. Let us give ourselves permission to read the calendar.

Let us find out the calendar ID of the calendar that you regularly use. We would be using this ID later when we make entries to the calendar. The following is the revised code for onCreate:

ListView lstCallLog;

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_call_log2_calendar);

lstCallLog = (ListView)findViewById(R.id.lstCallLog);

String<> strFrom = new String<> { CalendarContract.Events.CALENDAR_ID, CalendarContract.Events.TITLE };

int<> intTo = new int<> { R.id.txtCallLog, R.id.txtName };

//String strWhere = CallLog.Calls.CACHED_NAME + " IS NOT NULL";

Cursor curCallLog = getContentResolver().query(CalendarContract.Events.CONTENT_URI, null, null, null, null);

SimpleCursorAdapter adpCallLog = new SimpleCursorAdapter(this, R.layout.callloglist, curCallLog, strFrom,

intTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

lstCallLog.setAdapter(adpCallLog);

You can run the application now and check the various entries that have come from the calendar into the list view. The Hotmail calendar I use has an ID of 8. Let me define that as a class level variable and restore the code in onCreate to read from the call log instead of the calendar.

public class CallLog2CalendarActivity extends Activity {

public int MY_CALENDAR_ID = 8;

@Override

protected void onCreate(Bundle savedInstanceState) {

ListView lstCallLog;

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_call_log2_calendar);

lstCallLog = (ListView)findViewById(R.id.lstCallLog);

String<> strFrom = new String<> { CallLog.Calls.NUMBER, CallLog.Calls.CACHED_NAME };

int<> intTo = new int<> { R.id.txtCallLog, R.id.txtName };

String strWhere = CallLog.Calls.CACHED_NAME + " IS NOT NULL";

Cursor curCallLog = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, strWhere, null, null);

SimpleCursorAdapter adpCallLog = new SimpleCursorAdapter(this, R.layout.callloglist, curCallLog, strFrom,

intTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

lstCallLog.setAdapter(adpCallLog);

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.

getMenuInflater().inflate(R.menu.activity_call_log2_calendar, menu);

return true;

}

}

We would next have a button that will transfer the entries from the call log to the calendar. Let us define a string resource that will serve as the caption of the button. Change the main layout so that we have the button below the list view.

< 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=".CallLog2CalendarActivity" >

< Button android:id="@+id/btnCalendar" android: layout_width="fill_parent"

android:layout_height="wrap_content" android:text="@string/calendar_button_text"

android:layout_alignParentBottom="true"/>

< ListView

android:id="@+id/lstCallLog"

android:layout_width="fill_parent"

android:layout_height="fill_parent" android:layout_alignParentTop="true"

android:layout_above="@id/btnCalendar">

< /ListView>

< /RelativeLayout>

When you run the project, you will be able to see the button below the list view. Let us get permission to write on to the calendar.

Let us now check if we can make a dummy entry in the calendar by creating a click event for the button. The following is the revised code for onCreate:

ListView lstCallLog;

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_call_log2_calendar);

lstCallLog = (ListView)findViewById(R.id.lstCallLog);

String<> strFrom = new String<> { CallLog.Calls.NUMBER, CallLog.Calls.CACHED_NAME };

int<> intTo = new int<> { R.id.txtCallLog, R.id.txtName };

String strWhere = CallLog.Calls.CACHED_NAME + " IS NOT NULL";

Cursor curCallLog = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, strWhere, null, null);

SimpleCursorAdapter adpCallLog = new SimpleCursorAdapter(this, R.layout.callloglist, curCallLog, strFrom,

intTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

lstCallLog.setAdapter(adpCallLog);

Button btnCalendar;

btnCalendar = (Button)findViewById(R.id.btnCalendar);

btnCalendar.setOnClickListener(new OnClickListener() {

@Override public void onClick(View _view) {

newCalendarEvent();

}

});

Let us now copy the first entry from the call log to the calendar as a part of this dummy event.

@Override

protected void onCreate(Bundle savedInstanceState) {

ListView lstCallLog;

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_call_log2_calendar);

lstCallLog = (ListView)findViewById(R.id.lstCallLog);

String<> strFrom = new String<> { CallLog.Calls.NUMBER, CallLog.Calls.CACHED_NAME };

int<> intTo = new int<> { R.id.txtCallLog, R.id.txtName };

String strWhere = CallLog.Calls.CACHED_NAME + " IS NOT NULL";

final Cursor curCallLog = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, strWhere, null, null);

SimpleCursorAdapter adpCallLog = new SimpleCursorAdapter(this, R.layout.callloglist, curCallLog, strFrom,

intTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

lstCallLog.setAdapter(adpCallLog);

Button btnCalendar;

btnCalendar = (Button)findViewById(R.id.btnCalendar);

btnCalendar.setOnClickListener(new OnClickListener() {

@Override public void onClick(View _view) {

int intIndex;

String strTitle;

long lngStart;

long lngEnd;

curCallLog.moveToFirst();

intIndex = curCallLog.getColumnIndex(CallLog.Calls.CACHED_NAME);

strTitle = "CallLog: " + curCallLog.getString(intIndex);

intIndex = curCallLog.getColumnIndex(CallLog.Calls.DATE);

lngStart = curCallLog.getLong(intIndex);

intIndex = curCallLog.getColumnIndex(CallLog.Calls.DURATION);

lngEnd = lngStart + 1000 * curCallLog.getLong(intIndex);

newCalendarEvent(strTitle, lngStart, lngEnd);

}

});

}

public void newCalendarEvent(String strTitle, long lngStart, long lngEnd) {

ContentValues values = new ContentValues();

values.put(Events.DTSTART, lngStart);

values.put(Events.DTEND, lngEnd);

values.put(Events.TITLE, strTitle);

values.put(Events.CALENDAR_ID, MY_CALENDAR_ID);

values.put(Events.EVENT_TIMEZONE, "Asia/Kolkata");

getContentResolver().insert(Events.CONTENT_URI, values);

}

The call log that we have filtered out still contains missed calls. I do not want missed calls on my calendar, because strictly speaking, I did not speak to that contact. The WHERE clause is modified as follows:

String strWhere = CallLog.Calls.CACHED_NAME + " IS NOT NULL AND " + CallLog.Calls.TYPE + " < > " +

CallLog.Calls.MISSED_TYPE;

We do not want the calendar entry to be made if it is already there, let us write a function that checks if the entry already exists.

public boolean DoesNotExist(long lngStart, String strTitle) {

String strWhere = Events.DTSTART + " = " + lngStart + " AND " + Events.TITLE + " = ‘" + strTitle + "'";

final Cursor curCalendar = getContentResolver().query(Events.CONTENT_URI, null, strWhere, null, null);

if (curCalendar.getCount() == 0) return true;

else return false;

}

We will now make the calendar entry only if it does not exist. Here is the on click listener for the button that incorporates the check.

@Override public void onClick(View _view) {

int intIndex;

String strTitle;

long lngStart;

long lngEnd;

curCallLog.moveToFirst();

intIndex = curCallLog.getColumnIndex(CallLog.Calls.CACHED_NAME);

strTitle = "CallLog: " + curCallLog.getString(intIndex);

intIndex = curCallLog.getColumnIndex(CallLog.Calls.DATE);

lngStart = curCallLog.getLong(intIndex);

intIndex = curCallLog.getColumnIndex(CallLog.Calls.DURATION);

lngEnd = lngStart + 1000 * curCallLog.getLong(intIndex);

if (DoesNotExist(lngStart, strTitle)) newCalendarEvent(strTitle, lngStart, lngEnd);

}

I want the entries to be restricted to a specific set of friends, and not all entries in my contact. I can filter for one person, as indicated below.

String strWhere = CallLog.Calls.CACHED_NAME + " = ‘Manisha Saldanha' AND " + CallLog.Calls.TYPE + " < > " +

CallLog.Calls.MISSED_TYPE;

Here is how you can filter for two contacts.

String strWhere = "(" + CallLog.Calls.CACHED_NAME + " = ‘Manisha Saldanha' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Tejeswar Das') AND " +

CallLog.Calls.TYPE + " < > " +

CallLog.Calls.MISSED_TYPE;

Here is how I filtered for all the contacts I am interested in:

String strWhere = "(" + CallLog.Calls.CACHED_NAME + " = ‘Manisha Saldanha' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Tejeswar Das' OR " +

CallLog.Calls.CACHED_NAME + " = ‘K Murali Krishna' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Sabitri Sahu' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Shymala B' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Deepa Agrawal' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Swati Ranade' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Kiran Shah' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Kashappa Mashal' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Virendra Dharwadker' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Vinod Joshi' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Ravi Kumar' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Saroj Mallick') AND " +

CallLog.Calls.TYPE + " < > " + CallLog.Calls.MISSED_TYPE;

This is not a very elegant solution as the contact names have been hard coded. Every time we want to modify the contacts we want coped to the calendar, we have to come and modify the code. I would have preferred a configuration screen where I should be able to choose from all my existing contacts, but I will live with it for now.

The following loop in the click event transfers all the entries in the call log that we have filtered out, instead of just the first one.

@Override public void onClick(View _view) {

int intIndex;

String strTitle;

long lngStart;

long lngEnd;

curCallLog.moveToFirst();

while (!curCallLog.isAfterLast()) {

intIndex = curCallLog.getColumnIndex(CallLog.Calls.CACHED_NAME);

strTitle = "CallLog: " + curCallLog.getString(intIndex);

intIndex = curCallLog.getColumnIndex(CallLog.Calls.DATE);

lngStart = curCallLog.getLong(intIndex);

intIndex = curCallLog.getColumnIndex(CallLog.Calls.DURATION);

lngEnd = lngStart + 1000 * curCallLog.getLong(intIndex);

if (DoesNotExist(lngStart, strTitle)) newCalendarEvent(strTitle, lngStart, lngEnd);

curCallLog.moveToNext();

}

}

We are nearly done now; we need to verify if the entries in the call log are getting properly copied. We want to check what the dates in the call log are to make sure the right dates are copied on to the calendar. As the date in the call log is in terms of time elapsed from the epoch date, we need to convert it into the date format for display in the list view. Since the SimpleCursorAdapter is not meant to take a column after some computations have been done, let us create our own adapter. Now create the class derived from ResourceCursorAdapter.

public class MyAdapter extends ResourceCursorAdapter {

public MyAdapter(Context context, Cursor cur) {

super(context, R.layout.callloglist, cur, FLAG_REGISTER_CONTENT_OBSERVER);

}

@Override

public View newView(Context context, Cursor cur, ViewGroup parent) {

LayoutInflater li = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

return li.inflate(R.layout.callloglist, parent, false);

}

@Override

public void bindView(View view, Context context, Cursor cursor) {

// TODO Auto-generated method stub

TextView txtCallLog = (TextView)view.findViewById(R.id.txtCallLog);

TextView txtName = (TextView)view.findViewById(R.id.txtName);

txtCallLog.setText(cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME)));

txtName.setText(String.valueOf(cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE))));

}

}

Modify the code inside onCreate to use the new adapter. The date is not yet formatted, we will test in stages.

ListView lstCallLog;

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_call_log2_calendar);

lstCallLog = (ListView)findViewById(R.id.lstCallLog);

String strWhere = "(" + CallLog.Calls.CACHED_NAME + " = ‘Manisha Saldanha' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Tejeswar Das' OR " +

CallLog.Calls.CACHED_NAME + " = ‘K Murali Krishna'

OR " +

CallLog.Calls.CACHED_NAME + " = ‘Sabitri Sahu' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Shymala B' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Deepa Agrawal'

OR " +

CallLog.Calls.CACHED_NAME + " = ‘Swati Ranade' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Kiran Shah' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Kashappa Mashal'

OR " +

CallLog.Calls.CACHED_NAME + " = ‘Virendra

Dharwadker' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Vinod Joshi' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Ravi Kumar' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Saroj Mallick')

AND " +

CallLog.Calls.TYPE + " < > " + CallLog.Calls.MISSED_TYPE;

final Cursor curCallLog = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, strWhere, null, null);

MyAdapter adpCallLog = new MyAdapter(this,

curCallLog);

lstCallLog.setAdapter(adpCallLog);

Button btnCalendar;

btnCalendar = (Button)findViewById(R.id.btnCalendar);

btnCalendar.setOnClickListener(new OnClickListener() {

@Override public void onClick(View _view) {

int intIndex;

String strTitle;

long lngStart;

long lngEnd;

curCallLog.moveToFirst();

while (!curCallLog.isAfterLast()) {

intIndex = curCallLog.getColumnIndex(CallLog.Calls.CACHED_NAME);

strTitle = "CallLog: " + curCallLog.getString(intIndex);

intIndex = curCallLog.getColumnIndex(CallLog.Calls.DATE);

lngStart = curCallLog.getLong(intIndex);

intIndex = curCallLog.getColumnIndex(CallLog.Calls.DURATION);

lngEnd = lngStart + 1000 * curCallLog.getLong(intIndex);

if (DoesNotExist(lngStart, strTitle)) newCalendarEvent(strTitle, lngStart, lngEnd);

curCallLog.moveToNext();

}

}

});

If the application runs fine now without the date formatting, we have used our adapter successfully. Let us now change the bindView of the adapter to format the date as desired.

GregorianCalendar calStart;

SimpleDateFormat fmtStart;

TextView txtCallLog = (TextView)view.findViewById(R.id.txtCallLog);

TextView txtName = (TextView)view.findViewById(R.id.txtName);

calStart = new GregorianCalendar();

calStart.setTimeInMillis(cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE)));

fmtStart = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);

txtCallLog.setText(cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME)));

txtName.setText(fmtStart.format(calStart.getTime()));

We also want to exclude cases where the duration would be zero, for example, cancelled calls. We revise the WHERE condition for that.

String strWhere = "(" + CallLog.Calls.CACHED_NAME + " = ‘Manisha Saldanha' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Tejeswar Das' OR " +

CallLog.Calls.CACHED_NAME + " = ‘K Murali Krishna' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Sabitri Sahu' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Shymala B' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Deepa Agrawal' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Swati Ranade' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Kiran Shah' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Kashappa Mashal'

OR " +

CallLog.Calls.CACHED_NAME + " = ‘Virendra Dharwadker' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Vinod Joshi' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Ravi Kumar' OR " +

CallLog.Calls.CACHED_NAME + " = ‘Saroj Mallick')

AND " +

CallLog.Calls.TYPE + " < > " + CallLog.Calls.MISSED_TYPE + " AND " +

CallLog.Calls.DURATION + " > 0";

You can click the button now and verify the entries are getting copied as we want. Now that the date is getting displayed, you can verify whether the new calendar entries are as per the date and time in the call log. Note that we have not displayed the time of the call in our list view; you need to cross check with the original call log for that purpose.

Advertisment