/**
* Copyright (c) 2014, German Neuroinformatics Node (G-Node)
* Copyright (c) 2014, Shumail Mohy-ud-Din <shumailmohyuddin@gmail.com>
* License: BSD-3 (See LICENSE)
*/
package com.g_node.gca.abstracts;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.FilterQueryProvider;
import android.widget.ListView;
import android.widget.Toast;
import com.g_node.gcaa.R;
public class Abstracts extends Activity {
public static int cursorCount;
private EditText searchOption;
private ListView listView;
private AbstractCursorAdapter cursorAdapter;
private Cursor cursor;
private String SYNC_TIME_KEY = "com.g_node.gcaa.syncDateTime";
private String APP_PKG_NAME = "com.g_node.gcaa";
private String DB_CONSISTENCY_FLAG = "com.g_node.gcaa.dbConsistency";
private SharedPreferences appPreferences;
private String gTag = "GCA-Abstracts";
private final DatabaseHelper dbHelper = DatabaseHelper.getInstance(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_abstracts);
getActionBar().setDisplayHomeAsUpEnabled(true);
listView = (ListView)findViewById(R.id.AbsListView);
searchOption = (EditText)findViewById(R.id.abstractSearch);
appPreferences = Abstracts.this.getSharedPreferences(APP_PKG_NAME, Context.MODE_PRIVATE);
/*
* Get Writable Database
*/
dbHelper.open();
/*
* AsynchTask to parse the Abstracts JSON on a background thread
* ListView is also made ready in same AsyncTask's onPostExecute method
*/
AbstractJSONParsingTask jsonParsingAsyncTask = new AbstractJSONParsingTask();
jsonParsingAsyncTask.execute(); //starts asyncTask and handles all json parsing, listview populating optimally
}//end onCreate
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.abstracts_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.updateAbs:
{
//start an async task and check with server if there are any new abstracts
SynchronizeWithServer syncTask = new SynchronizeWithServer();
syncTask.execute();
return true;
}
default:
break;
}
return false;
}
@Override
protected void onDestroy() {
Log.i("exc", "on destry called");
super.onDestroy();
dbHelper.close("db");
// The activity is about to be destroyed.
}
private class AbstractJSONParsingTask extends AsyncTask<Void, Void, Void> {
private ProgressDialog Dialog = new ProgressDialog(Abstracts.this);
@Override
protected void onPreExecute()
{
Dialog.setMessage(Abstracts.this.getResources().getString(R.string.loading_abstracts_dialog_text));
Dialog.setCancelable(false);
Dialog.show();
}
/*
* doInBackgound handles all JSON parsing in background thread to avoid UI thread blockage
*/
@Override
protected Void doInBackground(Void... arg0) {
/*
* Query execution
*/
int a = dbHelper.getAbsCount();
Log.i(gTag, "data got rows : " + Integer.toString(a));
/*
* Get value of DB_CONSISTENCY_FLAG from sharedPref and check if
* database is consistent or not.
*/
int dbConsitencyFlagVal = appPreferences.getInt(DB_CONSISTENCY_FLAG, -1);
Log.d(gTag, "Val of DB_CONSISTENCY_FLAG - checking as Abstracts " +
"activity is opened: " + dbConsitencyFlagVal);
/*
* Get number of data to check whether database has any data or it's
* empty
*/
cursorCount = dbHelper.getAbsCount();
/*
* Check If Database is empty.
*/
if (cursorCount <= 0 || dbConsitencyFlagVal == -1) {
if(dbConsitencyFlagVal == -1) {
Log.d(gTag, "Database is inconsitent - flag: " + dbConsitencyFlagVal);
dbHelper.dropAllCreateAgain();
} else {
Log.d(gTag, "Database is consistent");
}
//call jsonParse function to get data from abstracts JSON file
InputStream jsonStream = Abstracts.this.getResources().openRawResource(R.raw.abstracts);
AbstractsJsonParse parseAbstractsJson = new AbstractsJsonParse(jsonStream, dbHelper);
parseAbstractsJson.jsonParse();
parseAbstractsJson.saveFromArrayListtoDB();
/*
* as the parsing and building of db process is completed here
* we need to update the consistent flag.
* (The next time if flag value won't be 1, all tables will be dropped
* and built again.)
*/
appPreferences.edit().putInt(DB_CONSISTENCY_FLAG, 1).apply();
Log.d(gTag, "Value of DB_CONSISTENCY_FLAG after initial json parsing: " + appPreferences.getInt(DB_CONSISTENCY_FLAG, -1));
/*
* Set value of sync time to a old date so when it syncs with
* server, it just gets everything
*/
String oldDummyTime = Abstracts.this.getResources().getString(R.string.old_dummy_sync_time);
appPreferences.edit().putString(SYNC_TIME_KEY, oldDummyTime).apply();
Log.d(gTag, "SYNC_TIME Val, Saving old time for first time or if DB " +
"is not consistent: " + appPreferences.getString(SYNC_TIME_KEY, null));
/*
* get number of cursor data
*/
cursorCount = dbHelper.getAbsCount();
}
return null;
}
@Override
protected void onPostExecute(Void result){
//set listView
cursor = dbHelper.getAllAbs();
cursorAdapter = new AbstractCursorAdapter(Abstracts.this, cursor,
AbstractCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listView.setAdapter(cursorAdapter);
listView.setTextFilterEnabled(true);
listView.setFastScrollEnabled(true);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View view, int position, long arg3) {
// TODO Auto-generated method stub
try {
cursor = (Cursor)cursorAdapter.getCursor();
/*
* Getting data from Cursor
*/
String Text = cursor.getString(cursor.getColumnIndexOrThrow("ABSRACT_TEXT"));
Log.i(gTag, "ABSTRACT_TEXT => " + Text);
String Title = cursor.getString(cursor.getColumnIndexOrThrow("TITLE"));
String Topic = cursor.getString(cursor.getColumnIndexOrThrow("TOPIC"));
String uuid = cursor.getString(cursor.getColumnIndexOrThrow("_id"));
String acknowledgements = cursor.getString(cursor.getColumnIndexOrThrow("ACKNOWLEDGEMENTS"));
Intent in = new Intent(getApplicationContext(), AbstractContent.class);
/*
* Passing data by Intent
*/
in.putExtra("abstracts", Text);
in.putExtra("Title", Title);
in.putExtra("Topic", Topic);
in.putExtra("value", uuid);
in.putExtra("acknowledgements", acknowledgements);
startActivity(in);
} catch (Exception e) {
e.printStackTrace();
}
}
});
Dialog.dismiss();
/*
* Searching filter to search data by Keywords, Title, Author Names, affiliation
*/
cursorAdapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
return dbHelper.findAbstractsWithString(constraint.toString());
}
});
searchOption.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence cs, int start, int before, int count) {
// TODO Auto-generated method stub
Abstracts.this.cursorAdapter.getFilter().filter(cs);
Abstracts.this.cursorAdapter.notifyDataSetChanged();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
} //end onPostExecute
} //end AsyncTask class
private class SynchronizeWithServer extends AsyncTask<Void, Void, Void> {
private int connectivityFlag = 0;
private int notificationFlag = 0;
private ProgressDialog Dialog = new ProgressDialog(Abstracts.this);
@Override
protected void onPreExecute() {
Dialog.setMessage("Please wait while app synchrinizes with Server...");
Dialog.setCancelable(false);
Dialog.show();
}
@Override
protected Void doInBackground(Void... params) {
if(!isNetworkAvailable()) { //if no internet access
connectivityFlag = -1;
} else {
/*
* internet is available
*/
InputStream in = null;
try {
Log.d("GCA-Sync", "Connecting...");
/*
* Read the last saved time and append to URL
* 1 - first we will read the last saved timestamp from sharedpreferences
* 2 - we'll formulate the URL by appending the timestamp at end
* - the structure of URL would be usually like
* http://127.0.0.1:9000/api/conferences/2311a932-1e89-4817-b767-a18f4a0b879f/abstracts/2015-07-09T08:24:50.833Z
* - note the timestamp in end. that's what we have stored in db and will append
* - The previous part of url before timestamp is read from strings.xml
* - so for formulating we'll read url from strings.xml and concatenate timestamp at end
*/
String lastSyncTime = appPreferences.getString(SYNC_TIME_KEY, null);
Log.d("GCA-Sync", "SYNC: Previous sync time for URL appending: " + lastSyncTime);
String urlString = getResources().getString(R.string.sync_url)+lastSyncTime;
Log.d("GCA-Sync", "SYNC: URL: " + urlString);
/*
* connecting with server
*/
URL url = new URL(urlString);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
Log.d("GCA-Sync", "Connection opened");
httpConnection.setRequestMethod("GET");
Log.d("GCA-Sync", "Method Set");
httpConnection.connect();
Log.d("GCA-Sync", "connected");
Log.d("GCA-Sync", "Response Code: " + httpConnection.getResponseCode());
if(httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
in = httpConnection.getInputStream();
Log.d("GCA-Sync", "Received Stream size: " + httpConnection.getContentLength() + " -- " + in.available());
if(httpConnection.getContentLength() <=20) { // 20 because even a single abstract object is returned; it'll be much more than 5
//Notify user that it's already upto date
notificationFlag = -1;
} else {
/*
* Some valid response. Need to synchronize
*/
/*
* set value of DB_CONSISTENCY_FLAG to -1 here
* Which we'll update back to 1 after synchronizing with server
*/
appPreferences.edit().putInt(DB_CONSISTENCY_FLAG, -1).apply();
Log.d(gTag, "DB_CONSISTENCY_FLAG set to -1 before syncing");
//in = MainActivity.this.getResources().openRawResource(R.raw.abstracts_up);
Log.d("GCA-Sync", "Received Response size: " + in + " -- " + in.available());
SyncAbstracts sync = new SyncAbstracts(dbHelper, in);
sync.doSync();
/*
* As synchronizing has been done here, update the value
* of DB_CONSISTENCY_FLAG back to 1 here
* If it's not updated back to 1 here, (because of some
* interuption like if device gets powered off while updating)
* the next time user opens Abstracts activity, it will check if
* value of flag is not 1, it will drop all db and build it again
* and set a old sync date, because with old sync date the next
* time it tries to sync with server, it'll fetch everything to sync again
* as the db is built again
*/
appPreferences.edit().putInt(DB_CONSISTENCY_FLAG, 1).apply();
Log.d(gTag, "DB_CONSISTENCY_FLAG value after syncing: " + appPreferences.getInt(DB_CONSISTENCY_FLAG, -1));
}
} else { //response from HTTP not 200
// some error in connecting - inform user.
Toast toast = Toast.makeText(getApplicationContext(), "Unable to Synchronize with Server. Try later", Toast.LENGTH_LONG);
toast.show();
}
httpConnection.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}// end else
return null;
}//end doInBackground
@Override
protected void onPostExecute(Void result){
if(connectivityFlag == -1) {
Dialog.dismiss();
connectivityFlag = 0;
Builder x = new AlertDialog.Builder(Abstracts.this);
x.setTitle("ERROR")
.setMessage("Unable to connect to Internet. Please ensure internet connectivity")
.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
}).setIcon(android.R.drawable.ic_dialog_alert).create().show();
} else {
Dialog.dismiss();
/*
* save the current time as last sync time
*/
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());
Log.d("GCA-Sync", "SYNC: Current time after sync: " + nowAsISO);
appPreferences.edit().putString(SYNC_TIME_KEY, nowAsISO).apply();
Log.d("GCA-Sync", "SYNC: current time form Shared Pref: " + appPreferences.getString(SYNC_TIME_KEY, null));
if(notificationFlag == -1) {
notificationFlag = 0;
Toast toast = Toast.makeText(getApplicationContext(), "Already up to date!",
Toast.LENGTH_LONG);
toast.show();
} else {
cursor = dbHelper.getAllAbs();
cursorAdapter.changeCursor(cursor);
cursorAdapter.notifyDataSetChanged();
//syncDbHelper.close("sync");
//notify that it's updated
Toast toast = Toast.makeText(getApplicationContext(), "Synchronized with server successfully.",
Toast.LENGTH_SHORT);
toast.show();
}
}
}
private boolean isNetworkAvailable() {
ConnectivityManager connectivityManager
= (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
} //end sync class
}