package org.ohmage.async;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.ohmage.AccountHelper;
import org.ohmage.ConfigHelper;
import org.ohmage.NotificationHelper;
import org.ohmage.OhmageApi;
import org.ohmage.OhmageApi.CampaignReadResponse;
import org.ohmage.OhmageApi.Response;
import org.ohmage.OhmageApi.Result;
import org.ohmage.R;
import org.ohmage.UserPreferencesHelper;
import org.ohmage.activity.ErrorDialogActivity;
import org.ohmage.db.DbContract;
import org.ohmage.db.DbContract.Campaigns;
import org.ohmage.db.Models.Campaign;
import org.ohmage.logprobe.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
/**
* A custom Loader that loads all of the installed applications.
*/
public class CampaignReadTask extends AuthenticatedTaskLoader<CampaignReadResponse> {
private static final String TAG = "CampaignReadTask";
private OhmageApi mApi;
private final Context mContext;
private final UserPreferencesHelper mPrefs;
public CampaignReadTask(Context context) {
super(context);
mContext = context;
mPrefs = new UserPreferencesHelper(mContext);
}
public CampaignReadTask(Context context, String username, String hashedPassword) {
super(context, username, hashedPassword);
mContext = context;
mPrefs = new UserPreferencesHelper(mContext);
}
@Override
public CampaignReadResponse loadInBackground() {
if(mApi == null)
mApi = new OhmageApi(mContext);
CampaignReadResponse response = mApi.campaignRead(ConfigHelper.serverUrl(), getUsername(), getHashedPassword(), OhmageApi.CLIENT_NAME, "short", null);
if (response.getResult() == Result.SUCCESS) {
ContentResolver cr = getContext().getContentResolver();
//build list of urns of all campaigns
Cursor cursor = cr.query(Campaigns.CONTENT_URI, new String [] {Campaigns.CAMPAIGN_URN, Campaigns.CAMPAIGN_CREATED, Campaigns.CAMPAIGN_STATUS}, null, null, null);
cursor.moveToFirst();
HashMap<String, Campaign> localCampaignUrns = new HashMap<String, Campaign>();
HashSet<String> toDelete = new HashSet<String>();
for (int i = 0; i < cursor.getCount(); i++) {
if(cursor.getInt(2) != Campaign.STATUS_REMOTE) {
// Here we store a list of campaigns we have downloaded
Campaign c = new Campaign();
c.mUrn = cursor.getString(cursor.getColumnIndex(Campaigns.CAMPAIGN_URN));
c.mCreationTimestamp = cursor.getString(cursor.getColumnIndex(Campaigns.CAMPAIGN_CREATED));
localCampaignUrns.put(c.mUrn, c);
} else {
// Here we store a list of campaigns we may have to delete if the server doesn't return them
toDelete.add(cursor.getString(0));
}
cursor.moveToNext();
}
cursor.close();
// The old urn thats used for single campaign mode. This has to be determined before the new data is downloaded in case the
// state changes. This is used to determine if there is a better choice for the single campaign mode after the download is complete.
final String oldUrn = Campaign.getSingleCampaign(getContext());
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
try { // parse response
JSONArray jsonItems = response.getMetadata().getJSONArray("items");
for(int i = 0; i < jsonItems.length(); i++) {
Campaign c = new Campaign();
JSONObject data = response.getData();
try {
c.mUrn = jsonItems.getString(i);
c.mName = data.getJSONObject(c.mUrn).getString("name");
c.mDescription = data.getJSONObject(c.mUrn).getString("description");
c.mCreationTimestamp = data.getJSONObject(c.mUrn).getString("creation_timestamp");
c.mDownloadTimestamp = null;
c.mXml = null;
c.mStatus = Campaign.STATUS_REMOTE;
c.mPrivacy = data.getJSONObject(c.mUrn).optString("privacy_state", Campaign.PRIVACY_UNKNOWN);
c.mIcon = data.getJSONObject(c.mUrn).optString("icon_url", null);
c.updated = startTime;
boolean running = data.getJSONObject(c.mUrn).getString("running_state").equalsIgnoreCase("running");
boolean participant = false;
JSONArray roles = data.getJSONObject(c.mUrn).getJSONArray("user_roles");
for(int j=0;j<roles.length();j++) {
if("participant".equals(roles.getString(j))) {
participant = true;
break;
}
}
if (localCampaignUrns.containsKey(c.mUrn)) { //campaign has already been downloaded
Campaign old = localCampaignUrns.get(c.mUrn);
localCampaignUrns.remove(c.mUrn);
ContentValues values = new ContentValues();
// FAISAL: include things here that may change at any time on the server
values.put(Campaigns.CAMPAIGN_PRIVACY, c.mPrivacy);
values.put(Campaigns.CAMPAIGN_UPDATED, c.updated);
if(!c.mCreationTimestamp.equals(old.mCreationTimestamp))
values.put(Campaigns.CAMPAIGN_STATUS, Campaign.STATUS_OUT_OF_DATE);
else if(running && !participant)
values.put(Campaigns.CAMPAIGN_STATUS, Campaign.STATUS_INVALID_USER_ROLE);
else
values.put(Campaigns.CAMPAIGN_STATUS, (running) ? Campaign.STATUS_READY : Campaign.STATUS_STOPPED);
operations.add(ContentProviderOperation.newUpdate(Campaigns.buildCampaignUri(c.mUrn)).withValues(values).build());
} else { //campaign has not been downloaded
if (running && participant) { //campaign is running and we are a participant
// We don't need to delete it
toDelete.remove(c.mUrn);
operations.add(ContentProviderOperation.newInsert(Campaigns.CONTENT_URI).withValues(c.toCV()).build());
}
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing json data for " + jsonItems.getString(i), e);
}
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing response json: 'items' key doesn't exist or is not a JSONArray", e);
}
for(String urn : toDelete) {
operations.add(ContentProviderOperation.newDelete(Campaigns.buildCampaignUri(urn)).build());
}
//leftover local campaigns were not returned by campaign read, therefore must be in some unavailable state
for (String urn : localCampaignUrns.keySet()) {
ContentValues values = new ContentValues();
values.put(Campaigns.CAMPAIGN_STATUS, Campaign.STATUS_NO_EXIST);
values.put(Campaigns.CAMPAIGN_UPDATED, startTime);
operations.add(ContentProviderOperation.newUpdate(Campaigns.buildCampaignUri(urn)).withValues(values).build());
}
if(!AccountHelper.accountExists()) {
Log.e(TAG, "User isn't logged in, terminating task");
return response;
}
try {
cr.applyBatch(DbContract.CONTENT_AUTHORITY, operations);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OperationApplicationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// If we are in single campaign mode, we should automatically download the xml for the best campaign
if(ConfigHelper.isSingleCampaignMode()) {
Campaign newCampaign = Campaign.getFirstAvaliableCampaign(getContext());
// If there is no good new campaign, the new campaign is different from the old one, or the old one is out of date, we should update it
if(newCampaign == null || TextUtils.isEmpty(newCampaign.mUrn) || !newCampaign.mUrn.equals(oldUrn) || newCampaign.mStatus == Campaign.STATUS_OUT_OF_DATE) {
// Download the new xml
if(newCampaign != null && !TextUtils.isEmpty(newCampaign.mUrn)) {
CampaignXmlDownloadTask campaignDownloadTask = new CampaignXmlDownloadTask(getContext(), newCampaign.mUrn, getUsername(), getHashedPassword());
campaignDownloadTask.registerListener(0, new OnLoadCompleteListener<OhmageApi.Response>() {
@Override
public void onLoadComplete(Loader<Response> loader, Response data) {
// If it was successful then we can set the single campaign
if(data.getResult() == Result.SUCCESS) {
if(!TextUtils.isEmpty(oldUrn)) {
// If we are removing the old campaign show the notification
Intent intent = new Intent(getContext(), ErrorDialogActivity.class);
intent.putExtra(ErrorDialogActivity.EXTRA_TITLE, getContext().getString(R.string.single_campaign_changed_title));
intent.putExtra(ErrorDialogActivity.EXTRA_MESSAGE, getContext().getString(R.string.single_campaign_changed_message));
NotificationHelper.showGeneralNotification(getContext(), getContext().getString(R.string.single_campaign_changed_title), getContext().getString(R.string.click_more_info), intent);
}
}
}
});
campaignDownloadTask.startLoading();
campaignDownloadTask.waitForLoader();
}
}
// Make all other campaigns remote
Campaign.ensureSingleCampaign(getContext());
}
}
return response;
}
public void setOhmageApi(OhmageApi api) {
mApi = api;
}
}