package org.commcare.provider; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.widget.Toast; import org.commcare.CommCareApplication; import org.commcare.activities.LoginMode; import org.commcare.dalvik.R; import org.commcare.models.database.SqlStorage; import org.commcare.models.encryption.ByteEncrypter; 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.models.legacy.LegacyInstallUtils; import org.commcare.preferences.CommCareServerPreferences; import org.commcare.tasks.DataPullTask; import org.commcare.tasks.ExternalManageKeyRecordTask; import org.commcare.tasks.ProcessAndSendTask; import org.commcare.tasks.ResultAndError; import org.commcare.tasks.templates.CommCareTask; import org.commcare.tasks.templates.CommCareTaskConnector; import org.commcare.utils.FormUploadResult; import org.commcare.utils.StorageUtils; import org.javarosa.core.model.User; import org.javarosa.core.services.locale.Localization; import java.math.BigInteger; import java.security.MessageDigest; import java.util.NoSuchElementException; import java.util.Vector; /** * This broadcast receiver is the central point for incoming API calls from other apps. * <p/> * 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 { private final CommCareTaskConnector dummyconnector = new CommCareTaskConnector() { @Override public void connectTask(CommCareTask task) { } @Override public void startBlockingForTask(int id) { } @Override public void stopBlockingForTask(int id) { } @Override public void taskCancelled() { } @Override public Object getReceiver() { return null; } @Override public void startTaskTransition() { } @Override public void stopTaskTransition() { } @Override public void hideTaskCancelButton() { } }; @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.instance().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 ("login".equals(b.getString("commcareaction"))) { String username = b.getString("username"); String password = b.getString("password"); tryLocalLogin(context, username, password); } else if ("sync".equals(b.getString("commcareaction"))) { boolean formsToSend = checkAndStartUnsentTask(context); if (!formsToSend) { //No unsent forms, just sync syncData(context); } } } private boolean checkAndStartUnsentTask(final Context context) { SqlStorage<FormRecord> storage = CommCareApplication.instance().getUserStorage(FormRecord.class); Vector<Integer> ids = StorageUtils.getUnsentOrUnprocessedFormIdsForCurrentApp(storage); 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)); } SharedPreferences settings = CommCareApplication.instance().getCurrentApp().getAppPreferences(); ProcessAndSendTask<Object> mProcess = new ProcessAndSendTask<Object>( context, settings.getString(CommCareServerPreferences.PREFS_SUBMISSION_URL_KEY, context.getString(R.string.PostURL))) { @Override protected void deliverResult(Object receiver, FormUploadResult result) { if (result == FormUploadResult.FULL_SUCCESS) { //OK, all forms sent, sync time syncData(context); } else if (result == FormUploadResult.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(); } } @Override protected void deliverUpdate(Object receiver, Long... update) { } @Override protected void deliverError(Object receiver, Exception e) { } }; mProcess.addSubmissionListener(CommCareApplication.instance().getSession().getListenerForSubmissionNotification()); mProcess.connect(dummyconnector); mProcess.execute(records); return true; } else { //Nothing. return false; } } private void syncData(final Context context) { User u = CommCareApplication.instance().getSession().getLoggedInUser(); SharedPreferences prefs = CommCareApplication.instance().getCurrentApp().getAppPreferences(); DataPullTask<Object> mDataPullTask = new DataPullTask<Object>( u.getUsername(), u.getCachedPwd(), u.getUniqueId(), prefs.getString(CommCareServerPreferences.PREFS_DATA_SERVER_KEY, context.getString(R.string.ota_restore_url)), context) { @Override protected void deliverResult(Object receiver, ResultAndError<PullTaskResult> resultAndErrorMessage) { PullTaskResult result = resultAndErrorMessage.data; if (result != PullTaskResult.DOWNLOAD_SUCCESS) { Toast.makeText(context, "CommCare couldn't sync. Please try to sync from CommCare directly for more information", Toast.LENGTH_LONG).show(); } else { Toast.makeText(context, "CommCare synced!", Toast.LENGTH_LONG).show(); } } @Override protected void deliverUpdate(Object receiver, Integer... update) { } @Override protected void deliverError(Object receiver, Exception e) { } }; mDataPullTask.connect(dummyconnector); mDataPullTask.execute(); } private boolean tryLocalLogin(Context context, String uname, String password) { try { UserKeyRecord matchingRecord = null; for (UserKeyRecord record : CommCareApplication.instance().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 = ByteEncrypter.unwrapByteArrayWithString(matchingRecord.getEncryptedKey(), password); if (matchingRecord.getType() == UserKeyRecord.TYPE_LEGACY_TRANSITION) { LegacyInstallUtils.transitionLegacyUserStorage(context, CommCareApplication.instance().getCurrentApp(), key, matchingRecord); } //TODO: See if it worked first? CommCareApplication.instance().startUserSession(key, matchingRecord, false); ExternalManageKeyRecordTask mKeyRecordTask = new ExternalManageKeyRecordTask(context, 0, matchingRecord.getUsername(), password, LoginMode.PASSWORD, CommCareApplication.instance().getCurrentApp(), false); mKeyRecordTask.connect(dummyconnector); mKeyRecordTask.execute(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } }