package org.commcare.provider; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.support.annotation.NonNull; import org.commcare.CommCareApplication; import org.commcare.utils.AndroidInstanceInitializer; import org.javarosa.core.model.instance.FormInstance; import org.javarosa.core.services.storage.IStorageIterator; import org.javarosa.core.services.storage.IStorageUtilityIndexed; import org.javarosa.model.xform.DataModelSerializer; import java.io.ByteArrayOutputStream; import java.io.IOException; /** * The fixture content provider defines the interface for external applications * to gain read only access to the current user's sandbox. External applications require * explicit permissions to access the content provider, and only data in the currently * logged in user's sandbox is provided. * * The FixtureDataAPI class is responsible for defining the data models and structures that * can be queried with the content provider. * * Note that this content provider requires a permission to be granted for read access. * * No write access is currently supported. * * @author wspride */ public class FixtureDataContentProvider extends ContentProvider { @Override public boolean onCreate() { return true; } @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //Standard dispatcher following Android best practices int match = FixtureDataAPI.UriMatch(uri); switch (match) { case FixtureDataAPI.MetadataColumns.LIST_INSTANCE_ID: return getFixtureNames(); case FixtureDataAPI.MetadataColumns.MATCH_INSTANCE_ID: return getFixtureForId(uri.getLastPathSegment()); } throw new IllegalArgumentException("URI: " + uri.toString() + " is not a valid content path for CommCare Case Data"); } @Override public String getType(@NonNull Uri uri) { int match = FixtureDataAPI.UriMatch(uri); switch (match) { case FixtureDataAPI.MetadataColumns.MATCH_ID: return FixtureDataAPI.MetadataColumns.FIXTURE_ID; case FixtureDataAPI.MetadataColumns.MATCH_INSTANCE_ID: return FixtureDataAPI.MetadataColumns.USER_ID; } return null; } /** * Return a cursor over the list of all fixture IDs and names */ private Cursor getFixtureNames() { MatrixCursor retCursor = new MatrixCursor(new String[]{FixtureDataAPI.MetadataColumns._ID, FixtureDataAPI.MetadataColumns.FIXTURE_ID}); IStorageUtilityIndexed<FormInstance> userFixtureStorage = CommCareApplication.instance().getUserStorage("fixture", FormInstance.class); for (IStorageIterator<FormInstance> userFixtures = userFixtureStorage.iterate(); userFixtures.hasMore(); ) { FormInstance fi = userFixtures.nextRecord(); String instanceId = fi.getInstanceId(); retCursor.addRow(new Object[]{fi.getID(), instanceId}); } return retCursor; } /** * Return a cursor to the fixture associated with this id */ private Cursor getFixtureForId(String instanceId) { MatrixCursor retCursor = new MatrixCursor(new String[]{FixtureDataAPI.MetadataColumns._ID, FixtureDataAPI.MetadataColumns.FIXTURE_ID, "content"}); IStorageUtilityIndexed<FormInstance> userFixtureStorage = CommCareApplication.instance().getUserStorage("fixture", FormInstance.class); for (IStorageIterator<FormInstance> userFixtures = userFixtureStorage.iterate(); userFixtures.hasMore(); ) { try { FormInstance fi = userFixtures.nextRecord(); String currentInstanceId = fi.getInstanceId(); if (instanceId.equals(currentInstanceId)) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataModelSerializer s = new DataModelSerializer(bos, new AndroidInstanceInitializer(null)); s.serialize(fi, fi.getRoot().getRef()); String dump = new String(bos.toByteArray()); retCursor.addRow(new Object[]{fi.getID(), fi.getInstanceId(), dump}); } } catch (IOException e) { e.printStackTrace(); } } return retCursor; } /** * All of the below are invalid due to the read-only nature of the content provider. It's not 100% clear from spec how to express * the read-only-ness. **/ @Override public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { // Case content provider is read only. //TODO: Throw an exception here? Read up on the spec return 0; } @Override public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { // Case content provider is read only. return 0; } @Override public Uri insert(@NonNull Uri uri, ContentValues values) { // TODO Auto-generated method stub return null; } }