Introduction:
This simple application import events from SharePoint site
and then add them to Android calendar frequently by using android Service in android mobile.
So
- Read Events from SharePoint site by using Web service (/_vti_bin/Lists.asmx)
- Service run in android mobile import these events
- Then add these events to Android calendar
- Notify the user by Notification activity
Tools & IDEs:
- Android SDK 2.3 (10 level)
- Eclipse
- My mobile (Samsung Ace)
- U2U CAML Query Builder
- SharePoint 2010 Site with Calendar list
Mail.xml (main Activity)
This activity collect the settings
that the background service will use them to collect the data and these are the settings:
- SharePoint Service URL
- To get data from SharePoint for Non-Microsoft project or programming language is to use SharePoint Web service which is (/_vti_bin/Lists.asmx). In my case I used (GetListItems) which return items from SharePoint list by passing CAML query or view.
- Row Limit
- Number of rows that (GetListItems) will return
- Option
- If this first time the application will import events from SharePoint so then option 1 (Get all Events) will be selected
- If this is not first time then option 2 (Get Last Events) will be selected. It will import all events after the last time imported ( last import time mention in green color under option 2)
- Button
- Start Service to start the android service ( as windows service in .net application) to run and collect data from SharePoint Calendar list and then save them to Android calendar. This service has a timer which makes it to run daily.
- Stop Service to stop the service and its timer
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:text="@string/lblServiceUrl"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
<EditText
android:id="@+id/txtServiceUrl"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:hint="@string/lblServiceUrl" />
<TextView
android:text="@string/lblRowLimit"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
<EditText
android:id="@+id/txtRowLimit"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:inputType="number"
android:maxLength="3" />
<TextView
android:text="@string/lblOptions"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
<RadioGroup
android:id="@+id/rdbGpOptions"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RadioButton
android:id="@+id/rdb1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lblGetAllEvents"
android:enabled="false" />
<RadioButton
android:id="@+id/rdb2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lblGetLastMonthEvents"
android:checked="true"
android:enabled="false" />
</RadioGroup>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#00ff00"
android:id="@+id/lblLastUpdate"
android:visibility="gone" />
<Button
android:id="@+id/btnStartService"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/lblStartService" />
<Button
android:id="@+id/btnStopService"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/lblStopService" />
</LinearLayout>
package sqlgoogler.blogspot.com;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
public class SyncSharePointCalendarActivity extends Activity {
private EditText txtServiceUrl ,txtRowLimit;
private Button btnStartService , btnStopService;
private RadioButton rdb1 , rdb2;
private TextView lblLastUpdate;
private SharedPreferences settings;
private String lastUpdate;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//Get settings from Shared Preferences
settings = getSharedPreferences(Constants.Pref_Name, 0);
txtServiceUrl = (EditText)findViewById(R.id.txtServiceUrl);
txtRowLimit = (EditText)findViewById(R.id.txtRowLimit);
lblLastUpdate = (TextView)findViewById(R.id.lblLastUpdate);
rdb1 = (RadioButton)findViewById(R.id.rdb1);
rdb2 = (RadioButton)findViewById(R.id.rdb2);
try
{
txtServiceUrl.setText(settings.getString(Constants.Service_URL_KEY, Constants.Service_URL_VALUE));
txtRowLimit.setText(settings.getString(Constants.Row_Limit_KEY, Constants.Row_Limit_VALUE));
lastUpdate = settings.getString(Constants.Last_Update_KEY, Constants.Last_Update_VALUE);
if(lastUpdate != "")
{
lblLastUpdate.setText(String.format("%s %s",getResources().getString(R.string.msgLastUpdate), lastUpdate));
lblLastUpdate.setVisibility(View.VISIBLE);
rdb2.setChecked(true);
rdb1.setChecked(false);
}
else
{
lblLastUpdate.setVisibility(View.GONE);
rdb2.setChecked(false);
rdb1.setChecked(true);
}
}
catch(Exception ex)
{
txtServiceUrl.setText( Constants.Service_URL_VALUE);
txtRowLimit.setText(Constants.Row_Limit_VALUE);
rdb2.setChecked(true);
rdb1.setChecked(false);
}
//Start Service Event
btnStartService = (Button) findViewById(R.id.btnStartService);
btnStartService.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//Save settings to Shared Preferences
SharedPreferences.Editor editor = settings.edit();
editor.putString(Constants.Service_URL_KEY,txtServiceUrl.getText().toString().trim());
editor.putString(Constants.Row_Limit_KEY,txtRowLimit.getText().toString().trim());
editor.commit();
//Start the service
startService(new Intent(getBaseContext(), SyncService.class));
showMessage(getResources().getString(R.string.StartMsg));
}
});
//Stop Service Event
btnStopService = (Button) findViewById(R.id.btnStopService);
btnStopService.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//Stop the service
stopService(new Intent(getBaseContext(), SyncService.class));
showMessage(getResources().getString(R.string.StopMsg));
}
});
}
private void showMessage(String msg)
{
Toast.makeText(getBaseContext(),msg, Toast.LENGTH_SHORT).show();
}
}
Second part of this application which did the most jobs is Android Service (SyncService):
this service did the following :
- Read settings from Shared Preferences (like SharePoint Service Url, Row Limit,..)
- This service run daily by using Timer
- Consume SharePoint Service and returned SOAP
- parsing this SOAP and add the return data to Android Calendar
- Show Notification about the status of import if it's successed or failed
package sqlgoogler.blogspot.com;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;
import android.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentValues;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.IBinder;
public class SyncService extends Service {
private Timer timer = new Timer();
private SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
private long updateInterval;
private SharedPreferences settings;
private String serviceUrl;
private String rowLimit;
private String lastUpdate;
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate()
{
super.onCreate();
//Read settings from SharedPreferences
settings = getSharedPreferences(Constants.Pref_Name, 0);
updateInterval = 1000 * 60 * 60 * 24; // By Day
updateInterval= 5000 * updateInterval;
serviceUrl = settings.getString(Constants.Service_URL_KEY, Constants.Service_URL_VALUE);
rowLimit = settings.getString(Constants.Row_Limit_KEY, Constants.Row_Limit_VALUE);
lastUpdate = settings.getString(Constants.Last_Update_KEY, Constants.Last_Update_VALUE);
_startService();
}
@Override
public void onDestroy()
{
super.onDestroy();
_shutdownService();
}
private void _startService()
{
timer.scheduleAtFixedRate(
new TimerTask() {
public void run() {
try{
doServiceWork();
Thread.sleep(updateInterval);
}catch(InterruptedException ie){
}
}
},
Constants.Delay_Interval,
updateInterval);
}
private void _shutdownService()
{
if (timer != null) timer.cancel();
Log.i(getClass().getSimpleName(), "Timer stopped...");
}
private void doServiceWork()
{
try
{
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(serviceUrl);
StringEntity se;
if(lastUpdate != "")
//CAML query for Last Events
se = new StringEntity( String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><GetListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\"><listName>eventsCal</listName>%s<rowLimit></rowLimit><viewName></viewName><query><Query><Where><Gt><FieldRef Name='EventDate' /><Value Type='DateTime'>%sZ</Value></Gt></Where><OrderBy><FieldRef Name='Title' /></OrderBy></Query></query></GetListItems></soap:Body></soap:Envelope>", rowLimit, lastUpdate), HTTP.UTF_8);
else
//CAML query for All Events
se = new StringEntity( String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><GetListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\"><listName>eventsCal</listName><rowLimit>%s</rowLimit><viewName></viewName><query><Query><OrderBy><FieldRef Name='Title' /></OrderBy></Query></query></GetListItems></soap:Body></soap:Envelope>", rowLimit), HTTP.UTF_8);
se.setContentType("text/xml");
httppost.setEntity(se);
HttpResponse httpresponse = httpclient.execute(httppost);
InputStream in = httpresponse.getEntity().getContent();
String str = inputStreamToString(in).toString();
readSoap(str);
//Show Notification
displayNotification(String.format("%s",getResources().getString(R.string.msgImported)));
//Set Last Update setting
SharedPreferences.Editor editor = settings.edit();
editor.putString(Constants.Last_Update_KEY, df.format(new java.util.Date()).toString());
editor.commit();
} catch (ClientProtocolException e) {
displayNotification(String.format("%s \n Error Details : %s",getResources().getString(R.string.msgImportedFailed),e.toString()));
} catch (IOException e) {
displayNotification(String.format("%s \n Error Details : %s",getResources().getString(R.string.msgImportedFailed),e.toString()));
}
}
private void readSoap(String in)
{
Document doc = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try
{
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(in));
doc = db.parse(is);
} catch (ParserConfigurationException e) {
} catch (SAXException e) {
} catch (IOException e) {
}
//---retrieve all the <z:row> nodes---
NodeList elements = doc.getElementsByTagName("z:row");
for (int i = 0; i < elements.getLength(); i++) {
Node itemNode = elements.item(i);
if (itemNode.getNodeType() == Node.ELEMENT_NODE)
{
//---convert the Node into an Element---
Element element = (Element) itemNode;
addEvent(element.getAttribute("ows_Title"), element.getAttribute("ows_EventDate"), element.getAttribute("ows_EndDate"), element.getAttribute("ows_Description"), element.getAttribute("ows_Location"));
}
}
}
private void addEvent(String title , String startEvent , String endEvent, String desc , String location)
{
try{
// date
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date1 = new Date();
Date date2 = new Date();
try {
date1 = format.parse(startEvent);
date2 = format.parse(endEvent);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Calendar cal = Calendar.getInstance();
cal.setTime(date1);
long startTime = cal.getTimeInMillis() + 1000;
cal.setTime(date2);
long endTime = cal.getTimeInMillis() + 1000;
ContentValues event = new ContentValues();
event.put("calendar_id", getCalendar_ID());
event.put("title", title);
event.put("description", desc);
event.put("eventLocation", location);
event.put("dtstart", startTime);
event.put("dtend", endTime);
event.put("allDay", 0);
getContentResolver().insert(Uri.parse("content://com.android.calendar/events"), event);
}catch(Exception ee){}
}
private int getCalendar_ID() {
int calendar_id = 0;
String[] projection = new String[] { "_id", "name" };
String selection = "selected=1";
String path = "calendars";
Cursor calendarCursor = getCalendarCursor(projection, selection, path);
if (calendarCursor != null && calendarCursor.moveToFirst()) {
int nameColumn = calendarCursor.getColumnIndex("name");
int idColumn = calendarCursor.getColumnIndex("_id");
do {
String calName = calendarCursor.getString(nameColumn);
String calId = calendarCursor.getString(idColumn);
if (calName != null) {
calendar_id = Integer.parseInt(calId);
}
} while (calendarCursor.moveToNext());
}
return calendar_id;
}
private Cursor getCalendarCursor(String[] projection, String selection, String path) {
Uri calendars = Uri.parse("content://calendar/" + path);
Cursor cCursor = null;
try
{
cCursor = getContentResolver().query(calendars, projection, selection, null, null);
}
catch (IllegalArgumentException e) {}
if (cCursor == null)
{
calendars = Uri.parse("content://com.android.calendar/" + path);
try
{
cCursor = getContentResolver().query(calendars, projection, selection, null, null);
}
catch (IllegalArgumentException e) {}
}
return cCursor;
}
private StringBuilder inputStreamToString(InputStream is)
{
String line = "";
StringBuilder total = new StringBuilder();
// Wrap a BufferedReader around the InputStream
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
// Read response until the end
try
{
while ((line = rd.readLine()) != null)
{
total.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// Return full string
return total;
}
protected void displayNotification(String msg)
{
Intent i = new Intent(this, NotificationActivity.class);
i.putExtra("notificationID", 1);
i.putExtra("msg", msg);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, i, 0);
NotificationManager nm = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
Notification notif = new Notification(
R.drawable.ic_launcher,getResources().getString(R.string.notificationdesc), System.currentTimeMillis());
CharSequence from = getResources().getString(R.string.notificationtitle);
CharSequence message = getResources().getString(R.string.notificationdesc);
notif.setLatestEventInfo(this, from, message, pendingIntent);
//---100ms delay, vibrate for 250ms, pause for 100 ms and
// then vibrate for 500ms---
notif.vibrate = new long[] { 100, 250, 100, 500};
nm.notify(1, notif);
}
}
so first thing this service will consume the SharePoint service and get items form events list
you will notice in the below code we built a String entity which contains the name of list and method name (GetListItems) and CAML Query
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(serviceUrl);
StringEntity se;
if(lastUpdate != "")
//CAML query for Last Events
se = new StringEntity( String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><GetListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\"><listName>eventsCal</listName>%s<rowLimit></rowLimit><viewName></viewName><query><Query><Where><Gt><FieldRef Name='EventDate' /><Value Type='DateTime'>%sZ</Value></Gt></Where><OrderBy><FieldRef Name='Title' /></OrderBy></Query></query></GetListItems></soap:Body></soap:Envelope>", rowLimit, lastUpdate), HTTP.UTF_8);
else
//CAML query for All Events
se = new StringEntity( String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><GetListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\"><listName>eventsCal</listName><rowLimit>%s</rowLimit><viewName></viewName><query><Query><OrderBy><FieldRef Name='Title' /></OrderBy></Query></query></GetListItems></soap:Body></soap:Envelope>", rowLimit), HTTP.UTF_8);
se.setContentType("text/xml");
httppost.setEntity(se);
HttpResponse httpresponse = httpclient.execute(httppost);
InputStream in = httpresponse.getEntity().getContent();
String str = inputStreamToString(in).toString();
so then we are parsing the return XML or SOAP and add these retrun items or events to Android Calendar.
Notes:
- I have tested this application in my Android mobile (Samsung Ace) with version 2.3 (level 10)
- this sample as starter and need more enchantments and test
- you can't test add event calendar to android using emulator because does not has calendar application
- You can download the code from here
2 comments:
Good job and idea Man...
really great job...
hats off to you
Post a Comment