package org.commcare.android.api; import java.math.BigInteger; import java.security.MessageDigest; import java.util.NoSuchElementException; import java.util.Vector; import org.commcare.android.crypt.CryptUtil; import org.commcare.android.database.SqlStorage; import org.commcare.android.database.app.models.UserKeyRecord; import org.commcare.android.database.global.models.AndroidSharedKeyRecord; import org.commcare.android.database.user.models.FormRecord; import org.commcare.android.database.user.models.User; import org.commcare.android.db.legacy.LegacyInstallUtils; import org.commcare.android.tasks.DataPullTask; import org.commcare.android.tasks.ManageKeyRecordListener; import org.commcare.android.tasks.ManageKeyRecordTask; import org.commcare.android.tasks.ProcessAndSendTask; import org.commcare.android.tasks.ProcessTaskListener; import org.commcare.android.tasks.templates.CommCareTask; import org.commcare.android.tasks.templates.CommCareTaskConnector; import org.commcare.android.tasks.templates.HttpCalloutTask.HttpCalloutOutcomes; import org.commcare.android.util.FormUploadUtil; import org.commcare.android.util.SessionUnavailableException; import org.commcare.dalvik.R; import org.commcare.dalvik.application.CommCareApplication; import org.javarosa.core.services.locale.Localization; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.widget.Toast; /** * This broadcast receiver is the central point for incoming API calls from other apps. * * Right now it's a mess, but at some point we'll go ahead and pull out most of the * things you can do here as * * @author ctsims * */ public class ExternalApiReceiver extends BroadcastReceiver { CommCareTaskConnector dummyconnector = new CommCareTaskConnector() { /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTaskConnector#connectTask(org.commcare.android.tasks.templates.CommCareTask) */ @Override public void connectTask(CommCareTask task) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTaskConnector#startBlockingForTask(int) */ @Override public void startBlockingForTask(int id) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTaskConnector#stopBlockingForTask(int) */ @Override public void stopBlockingForTask(int id) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTaskConnector#taskCancelled(int) */ @Override public void taskCancelled(int id) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTaskConnector#getReceiver() */ @Override public Object getReceiver() { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTaskConnector#startTaskTransition() */ @Override public void startTaskTransition() { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTaskConnector#stopTaskTransition() */ @Override public void stopTaskTransition() { // TODO Auto-generated method stub } }; /* (non-Javadoc) * @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent) */ @Override public void onReceive(Context context, Intent intent) { if(!intent.hasExtra(AndroidSharedKeyRecord.EXTRA_KEY_ID)) { return; } String keyId = intent.getStringExtra(AndroidSharedKeyRecord.EXTRA_KEY_ID); SqlStorage<AndroidSharedKeyRecord> storage = CommCareApplication._().getGlobalStorage(AndroidSharedKeyRecord.class); AndroidSharedKeyRecord sharingKey; try { sharingKey = storage.getRecordForValue(AndroidSharedKeyRecord.META_KEY_ID, keyId); } catch(NoSuchElementException nsee) { //No valid key record; return; } Bundle b = sharingKey.getIncomingCallout(intent); performAction(context, b); } private void performAction(final Context context, Bundle b) { if(b.getString("commcareaction").equals("login")) { String username = b.getString("username"); String password = b.getString("password"); tryLocalLogin(context, username, password); } else if(b.getString("commcareaction").equals("sync")) { boolean formsToSend = checkAndStartUnsentTask(context, new ProcessTaskListener() { public void processTaskAllProcessed() { //Don't cancel the dialog, we need it to stay in the foreground to ensure things are set } public void processAndSendFinished(int result, int successfulSends) { } }); if(!formsToSend) { //No unsent forms, just sync syncData(context); } } } protected boolean checkAndStartUnsentTask(final Context context, ProcessTaskListener listener) throws SessionUnavailableException { SqlStorage<FormRecord> storage = CommCareApplication._().getUserStorage(FormRecord.class); //Get all forms which are either unsent or unprocessed Vector<Integer> ids = storage.getIDsForValues(new String[] {FormRecord.META_STATUS}, new Object[] {FormRecord.STATUS_UNSENT}); ids.addAll(storage.getIDsForValues(new String[] {FormRecord.META_STATUS}, new Object[] {FormRecord.STATUS_COMPLETE})); if(ids.size() > 0) { FormRecord[] records = new FormRecord[ids.size()]; for(int i = 0 ; i < ids.size() ; ++i) { records[i] = storage.read(ids.elementAt(i).intValue()); } SharedPreferences settings = CommCareApplication._().getCurrentApp().getAppPreferences(); ProcessAndSendTask<Object> mProcess = new ProcessAndSendTask<Object>(context, settings.getString("PostURL", context.getString(R.string.PostURL))) { /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTask#deliverResult(java.lang.Object, java.lang.Object) */ @Override protected void deliverResult(Object receiver, Integer result) { if(result == FormUploadUtil.FULL_SUCCESS) { //OK, all forms sent, sync time syncData(context); } else if(result == FormUploadUtil.FAILURE) { Toast.makeText(context, Localization.get("sync.fail.unsent"), Toast.LENGTH_LONG).show(); } else { Toast.makeText(context, Localization.get("sync.fail.unsent"), Toast.LENGTH_LONG).show(); } } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTask#deliverUpdate(java.lang.Object, java.lang.Object[]) */ @Override protected void deliverUpdate(Object receiver, Long... update) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTask#deliverError(java.lang.Object, java.lang.Exception) */ @Override protected void deliverError(Object receiver, Exception e) { // TODO Auto-generated method stub } }; mProcess.setListeners(CommCareApplication._().getSession().startDataSubmissionListener()); mProcess.connect(dummyconnector); mProcess.execute(records); return true; } else { //Nothing. return false; } } private void syncData(final Context c) { User u = CommCareApplication._().getSession().getLoggedInUser(); SharedPreferences prefs = CommCareApplication._().getCurrentApp().getAppPreferences(); DataPullTask<Object> mDataPullTask = new DataPullTask<Object>(u.getUsername(), u.getCachedPwd(), prefs.getString("ota-restore-url",c.getString(R.string.ota_restore_url)), "", c) { /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTask#deliverResult(java.lang.Object, java.lang.Object) */ @Override protected void deliverResult(Object receiver, Integer result) { if(result != DataPullTask.DOWNLOAD_SUCCESS) { Toast.makeText(c, "CommCare couldn't sync. Please try to sync from CommCare directly for more information", Toast.LENGTH_LONG).show(); } else { Toast.makeText(c, "CommCare synced!", Toast.LENGTH_LONG).show(); } } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTask#deliverUpdate(java.lang.Object, java.lang.Object[]) */ @Override protected void deliverUpdate(Object receiver, Integer... update) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTask#deliverError(java.lang.Object, java.lang.Exception) */ @Override protected void deliverError(Object receiver, Exception e) { // TODO Auto-generated method stub } }; mDataPullTask.connect(dummyconnector); mDataPullTask.execute(); } private boolean tryLocalLogin(Context context, String uname, String password) { try{ UserKeyRecord matchingRecord = null; for(UserKeyRecord record : CommCareApplication._().getCurrentApp().getStorage(UserKeyRecord.class)) { if(!record.getUsername().equals(uname)) { continue; } String hash = record.getPasswordHash(); if(hash.contains("$")) { String alg = "sha1"; String salt = hash.split("\\$")[1]; String check = hash.split("\\$")[2]; MessageDigest md = MessageDigest.getInstance("SHA-1"); BigInteger number = new BigInteger(1, md.digest((salt+password).getBytes())); String hashed = number.toString(16); while(hashed.length() < check.length()) { hashed = "0" + hashed; } if(hash.equals(alg + "$" + salt + "$" + hashed)) { matchingRecord = record; } } } if(matchingRecord == null) { return false; } //TODO: Extract this byte[] key = CryptUtil.unWrapKey(matchingRecord.getEncryptedKey(), password); if(matchingRecord.getType() == UserKeyRecord.TYPE_LEGACY_TRANSITION) { LegacyInstallUtils.transitionLegacyUserStorage(context, CommCareApplication._().getCurrentApp(), key, matchingRecord); } //TODO: See if it worked first? CommCareApplication._().logIn(key, matchingRecord); new ManageKeyRecordTask<Object>(context, 0, matchingRecord.getUsername(), password, CommCareApplication._().getCurrentApp(), new ManageKeyRecordListener() { @Override public void keysLoginComplete(Object o) { // TODO Auto-generated method stub } @Override public void keysReadyForSync(Object o) { // TODO Auto-generated method stub } @Override public void keysDoneOther(Object o, HttpCalloutOutcomes outcome) { // TODO Auto-generated method stub } }) { /* * (non-Javadoc) * @see org.commcare.android.tasks.templates.CommCareTask#deliverUpdate(java.lang.Object, java.lang.Object[]) */ @Override protected void deliverUpdate(Object r, String... update) { } }.execute(); return true; }catch (Exception e) { e.printStackTrace(); return false; } } }