Skip to content

Commit

Permalink
plusonelabs#356 Extract content provider-related functions into a sep…
Browse files Browse the repository at this point in the history
…arate MyContentResolver class to ease mocking and simplify code.
  • Loading branch information
yvolk committed Dec 29, 2019
1 parent b45122d commit 7fd5dcf
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 184 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.andstatus.todoagenda.calendar;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
Expand All @@ -10,7 +9,6 @@
import android.provider.CalendarContract;
import android.provider.CalendarContract.Attendees;
import android.provider.CalendarContract.Instances;
import android.util.Log;
import android.util.SparseArray;

import androidx.annotation.NonNull;
Expand All @@ -19,11 +17,8 @@
import org.andstatus.todoagenda.prefs.OrderedEventSource;
import org.andstatus.todoagenda.provider.EventProvider;
import org.andstatus.todoagenda.provider.EventProviderType;
import org.andstatus.todoagenda.provider.QueryResult;
import org.andstatus.todoagenda.provider.QueryResultsStorage;
import org.andstatus.todoagenda.util.CalendarIntentUtil;
import org.andstatus.todoagenda.util.DateUtil;
import org.andstatus.todoagenda.util.PermissionsUtil;
import org.joda.time.DateTime;

import java.util.ArrayList;
Expand All @@ -47,7 +42,8 @@ public CalendarEventProvider(EventProviderType type, Context context, int widget

List<CalendarEvent> queryEvents() {
initialiseParameters();
if (PermissionsUtil.isPermissionNeeded(context, type.permission) ||
myContentResolver.onQueryEvents();
if (myContentResolver.isPermissionNeeded(context, type.permission) ||
getSettings().getActiveEventSources(type).isEmpty()) {
return Collections.emptyList();
}
Expand Down Expand Up @@ -138,34 +134,14 @@ private String getCalendarSelection() {
}

private List<CalendarEvent> queryList(Uri uri, String selection) {
List<CalendarEvent> eventList = new ArrayList<>();
QueryResult result = new QueryResult(type, getSettings(), uri, getProjection(),
selection, null, EVENT_SORT_ORDER);
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, getProjection(),
selection, null, EVENT_SORT_ORDER);
if (cursor != null) {
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
if (QueryResultsStorage.getNeedToStoreResults()) {
result.addRow(cursor);
}
return myContentResolver.foldEvents(uri, getProjection(), selection, null, EVENT_SORT_ORDER,
new ArrayList<>(), eventList -> cursor -> {
CalendarEvent event = createCalendarEvent(cursor);
if (!eventList.contains(event) && !mKeywordsFilter.matched(event.getTitle())) {
eventList.add(event);
}
}
}
} catch (Exception e) {
Log.w(TAG, "Failed to queryList uri:" + uri + ", selection:" + selection, e);
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
QueryResultsStorage.store(result);
return eventList;
return eventList;
});
}

public static String[] getProjection() {
Expand Down Expand Up @@ -247,31 +223,20 @@ private int getEventColor(Cursor cursor) {

@Override
public List<EventSource> fetchAvailableSources() {
List<EventSource> eventSources = new ArrayList<>();
Uri.Builder builder = CalendarContract.Calendars.CONTENT_URI.buildUpon();
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = null;
try {
cursor = contentResolver.query(builder.build(), EVENT_SOURCES_PROJECTION, null, null, null);
if (cursor == null) {
return eventSources;
}

int indId = cursor.getColumnIndex(CalendarContract.Calendars._ID);
int indTitle = cursor.getColumnIndex(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME);
int indSummary = cursor.getColumnIndex(CalendarContract.Calendars.ACCOUNT_NAME);
int indColor = cursor.getColumnIndex(CalendarContract.Calendars.CALENDAR_COLOR);
while (cursor.moveToNext()) {
EventSource source = new EventSource(type, cursor.getInt(indId), cursor.getString(indTitle),
cursor.getString(indSummary), cursor.getInt(indColor), true);
eventSources.add(source);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return eventSources;
return myContentResolver.foldAvailableSources(
CalendarContract.Calendars.CONTENT_URI.buildUpon().build(),
EVENT_SOURCES_PROJECTION,
new ArrayList<>(),
eventSources -> cursor -> {
int indId = cursor.getColumnIndex(CalendarContract.Calendars._ID);
int indTitle = cursor.getColumnIndex(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME);
int indSummary = cursor.getColumnIndex(CalendarContract.Calendars.ACCOUNT_NAME);
int indColor = cursor.getColumnIndex(CalendarContract.Calendars.CALENDAR_COLOR);
EventSource source = new EventSource(type, cursor.getInt(indId), cursor.getString(indTitle),
cursor.getString(indSummary), cursor.getInt(indColor), true);
eventSources.add(source);
return eventSources;
});
}

public Intent createViewEventIntent(CalendarEvent event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class EventProvider {
public final EventProviderType type;
public final Context context;
public final int widgetId;
protected final MyContentResolver myContentResolver;

// Below are parameters, which may change in settings
protected DateTimeZone zone;
Expand All @@ -48,6 +49,7 @@ public EventProvider(EventProviderType type, Context context, int widgetId) {
this.type = type;
this.context = context;
this.widgetId = widgetId;
myContentResolver = new MyContentResolver(type, context, widgetId);
}

protected void initialiseParameters() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.andstatus.todoagenda.provider;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.andstatus.todoagenda.prefs.AllSettings;
import org.andstatus.todoagenda.prefs.InstanceSettings;
import org.andstatus.todoagenda.util.PermissionsUtil;

import java.util.Arrays;
import java.util.function.Function;

/**
* Testing and mocking Calendar and Tasks Providers
*
* @author [email protected]
*/
public class MyContentResolver {
private final static String TAG = MyContentResolver.class.getSimpleName();
final EventProviderType type;
final Context context;
final int widgetId;
private volatile int requestsCounter = 0;

public MyContentResolver(EventProviderType type, Context context, int widgetId) {
this.type = type;
this.context = context;
this.widgetId = widgetId;
}

@NonNull
public InstanceSettings getSettings() {
return AllSettings.instanceFromId(context, widgetId);
}

public boolean isPermissionNeeded(Context context, String permission) {
return PermissionsUtil.isPermissionNeeded(context, permission);
}

public <R> R foldAvailableSources(@NonNull Uri uri, @Nullable String[] projection,
R identity, Function<R, Function<Cursor, R>> foldingFunction) {
R folded = identity;
try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) {
if (cursor != null) {
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
folded = foldingFunction.apply(folded).apply(cursor);
}
}
} catch (IllegalArgumentException e) {
Log.d(type.name(), widgetId + " " + e.getMessage());
} catch (Exception e) {
Log.w(type.name(), widgetId + " Failed to fetch available sources" +
" uri:" + uri +
", projection:" + Arrays.toString(projection), e);
}
return folded;
}

public void onQueryEvents() {
requestsCounter = 0;
}

public <R> R foldEvents(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder,
R identity, Function<R, Function<Cursor, R>> foldingFunction) {
R folded = identity;
boolean needToStoreResults = QueryResultsStorage.getNeedToStoreResults();
QueryResult result = needToStoreResults
? new QueryResult(type, getSettings(), uri, projection, selection, null, sortOrder)
: null;
try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder)) {
if (cursor != null) {
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
if (needToStoreResults) result.addRow(cursor);
folded = foldingFunction.apply(folded).apply(cursor);
}
}
} catch (Exception e) {
Log.w(type.name(), widgetId + " Failed to query events" +
" uri:" + uri +
", projection:" + Arrays.toString(projection) +
", selection:" + selection +
", args:" + Arrays.toString(selectionArgs) +
", sort:" + sortOrder, e);
}
if (needToStoreResults) QueryResultsStorage.store(result);
return folded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import java.util.List;

/**
* Useful for logging and mocking CalendarContentProvider
* Useful for logging and mocking ContentProviders
*
* @author [email protected]
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected void initialiseParameters() {

List<TaskEvent> queryEvents() {
initialiseParameters();
if (PermissionsUtil.isPermissionNeeded(context, type.permission) ||
if (myContentResolver.isPermissionNeeded(context, type.permission) ||
getSettings().getActiveEventSources(type).isEmpty()) {
return Collections.emptyList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
import org.andstatus.todoagenda.prefs.FilterMode;
import org.andstatus.todoagenda.prefs.OrderedEventSource;
import org.andstatus.todoagenda.provider.EventProviderType;
import org.andstatus.todoagenda.provider.QueryResult;
import org.andstatus.todoagenda.provider.QueryResultsStorage;
import org.andstatus.todoagenda.task.AbstractTaskProvider;
import org.andstatus.todoagenda.task.TaskEvent;
import org.andstatus.todoagenda.task.TaskStatus;
Expand All @@ -31,6 +29,8 @@ public DmfsOpenTasksProvider(EventProviderType type, Context context, int widget

@Override
public List<TaskEvent> queryTasks() {
myContentResolver.onQueryEvents();

Uri uri = DmfsOpenTasksContract.Tasks.PROVIDER_URI;
String[] projection = {
DmfsOpenTasksContract.Tasks.COLUMN_LIST_ID,
Expand All @@ -43,37 +43,14 @@ public List<TaskEvent> queryTasks() {
};
String where = getWhereClause();

QueryResult result = new QueryResult(type, getSettings(), uri, projection, where, null, null);

Cursor cursor;
try {
cursor = context.getContentResolver().query(uri, projection, where, null, null);
} catch (IllegalArgumentException e) {
cursor = null;
}
if (cursor == null) {
return new ArrayList<>();
}

List<TaskEvent> tasks = new ArrayList<>();
try {
while (cursor.moveToNext()) {
if (QueryResultsStorage.getNeedToStoreResults()) {
result.addRow(cursor);
}

TaskEvent task = createTask(cursor);
if (matchedFilter(task)) {
tasks.add(task);
}
}
} finally {
cursor.close();
}

QueryResultsStorage.store(result);

return tasks;
return myContentResolver.foldEvents(uri, projection, where, null, null,
new ArrayList<>(), tasks -> cursor -> {
TaskEvent task = createTask(cursor);
if (matchedFilter(task)) {
tasks.add(task);
}
return tasks;
});
}

private String getWhereClause() {
Expand Down Expand Up @@ -161,39 +138,27 @@ private TaskStatus loadStatus(Cursor cursor) {

@Override
public List<EventSource> fetchAvailableSources() {
ArrayList<EventSource> eventSources = new ArrayList<>();

String[] projection = {
DmfsOpenTasksContract.TaskLists.COLUMN_ID,
DmfsOpenTasksContract.TaskLists.COLUMN_NAME,
DmfsOpenTasksContract.TaskLists.COLUMN_COLOR,
DmfsOpenTasksContract.TaskLists.COLUMN_ACCOUNT_NAME,
};
Cursor cursor;
try {
cursor = context.getContentResolver().query(DmfsOpenTasksContract.TaskLists.PROVIDER_URI, projection, null, null, null);
} catch (IllegalArgumentException e) {
cursor = null;
}
if (cursor == null) {
return eventSources;
}

int indId = cursor.getColumnIndex(DmfsOpenTasksContract.TaskLists.COLUMN_ID);
int indTitle = cursor.getColumnIndex(DmfsOpenTasksContract.TaskLists.COLUMN_NAME);
int indColor = cursor.getColumnIndex(DmfsOpenTasksContract.TaskLists.COLUMN_COLOR);
int indSummary = cursor.getColumnIndex(DmfsOpenTasksContract.TaskLists.COLUMN_ACCOUNT_NAME);
try {
while (cursor.moveToNext()) {
EventSource eventSource = new EventSource(type, cursor.getInt(indId), cursor.getString(indTitle),
cursor.getString(indSummary), cursor.getInt(indColor), true);
eventSources.add(eventSource);
}
} finally {
cursor.close();
}

return eventSources;
return myContentResolver.foldAvailableSources(
DmfsOpenTasksContract.TaskLists.PROVIDER_URI,
projection,
new ArrayList<>(),
eventSources -> cursor -> {
int indId = cursor.getColumnIndex(DmfsOpenTasksContract.TaskLists.COLUMN_ID);
int indTitle = cursor.getColumnIndex(DmfsOpenTasksContract.TaskLists.COLUMN_NAME);
int indColor = cursor.getColumnIndex(DmfsOpenTasksContract.TaskLists.COLUMN_COLOR);
int indSummary = cursor.getColumnIndex(DmfsOpenTasksContract.TaskLists.COLUMN_ACCOUNT_NAME);
EventSource source = new EventSource(type, cursor.getInt(indId), cursor.getString(indTitle),
cursor.getString(indSummary), cursor.getInt(indColor), true);
eventSources.add(source);
return eventSources;
});
}

@Override
Expand Down
Loading

0 comments on commit 7fd5dcf

Please sign in to comment.