package com.quran.labs.androidquran.data; import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; import android.net.Uri; import android.provider.BaseColumns; import android.support.annotation.NonNull; import com.crashlytics.android.Crashlytics; import com.quran.labs.androidquran.BuildConfig; import com.quran.labs.androidquran.QuranApplication; import com.quran.labs.androidquran.R; import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.database.DatabaseHandler; import com.quran.labs.androidquran.database.DatabaseUtils; import com.quran.labs.androidquran.database.TranslationsDBAdapter; import com.quran.labs.androidquran.util.QuranFileUtils; import com.quran.labs.androidquran.util.QuranSettings; import com.quran.labs.androidquran.util.QuranUtils; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import timber.log.Timber; public class QuranDataProvider extends ContentProvider { public static String AUTHORITY = BuildConfig.APPLICATION_ID + ".data.QuranDataProvider"; public static final Uri SEARCH_URI = Uri.parse("content://" + AUTHORITY + "/quran/search"); public static final String VERSES_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.com.quran.labs.androidquran"; public static final String QURAN_ARABIC_DATABASE = QuranFileConstants.ARABIC_DATABASE; // UriMatcher stuff private static final int SEARCH_VERSES = 0; private static final int SEARCH_SUGGEST = 1; private static final UriMatcher uriMatcher = buildUriMatcher(); private boolean didInject; @Inject QuranSettings quranSettings; @Inject TranslationsDBAdapter translationsDBAdapter; private static UriMatcher buildUriMatcher() { UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI(AUTHORITY, "quran/search", SEARCH_VERSES); matcher.addURI(AUTHORITY, "quran/search/*", SEARCH_VERSES); matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST); matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST); return matcher; } @Override public boolean onCreate() { return true; } @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Context context = getContext(); if (!didInject) { Context appContext = context == null ? null : context.getApplicationContext(); if (appContext instanceof QuranApplication) { ((QuranApplication) appContext).getApplicationComponent().inject(this); didInject = true; } else { Timber.e("unable to inject QuranDataProvider"); return null; } } Crashlytics.log("uri: " + uri.toString()); switch (uriMatcher.match(uri)) { case SEARCH_SUGGEST: { if (selectionArgs == null) { throw new IllegalArgumentException( "selectionArgs must be provided for the Uri: " + uri); } return getSuggestions(selectionArgs[0]); } case SEARCH_VERSES: { if (selectionArgs == null) { throw new IllegalArgumentException( "selectionArgs must be provided for the Uri: " + uri); } return search(selectionArgs[0]); } default: { throw new IllegalArgumentException("Unknown Uri: " + uri); } } } private Cursor search(String query) { return search(query, getAvailableTranslations()); } private List<LocalTranslation> getAvailableTranslations() { return translationsDBAdapter.getTranslations(); } private Cursor getSuggestions(String query) { if (query.length() < 3) { return null; } final boolean queryIsArabic = QuranUtils.doesStringContainArabic(query); final boolean haveArabic = queryIsArabic && QuranFileUtils.hasTranslation(getContext(), QURAN_ARABIC_DATABASE); List<LocalTranslation> translations = getAvailableTranslations(); if (translations.size() == 0 && (queryIsArabic && !haveArabic)) { return null; } int total = translations.size() + (haveArabic ? 1 : 0); int start = haveArabic ? -1 : 0; String[] cols = new String[] { BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_TEXT_2, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID }; MatrixCursor mc = new MatrixCursor(cols); Context context = getContext(); boolean gotResults = false; for (int i = start; i < total; i++) { if (gotResults) { continue; } String database; if (i < 0) { database = QURAN_ARABIC_DATABASE; } else { LocalTranslation translation = translations.get(i); // skip non-arabic databases if the query is in arabic if (queryIsArabic && translation.languageCode != null && !"ar".equals(translation.languageCode)) { continue; } else if (!queryIsArabic && "ar".equals(translation.languageCode)) { // skip arabic databases when the query isn't arabic continue; } database = translation.filename; } Cursor suggestions = null; try { suggestions = search(query, database, false); if (suggestions != null && suggestions.moveToFirst()) { do { int sura = suggestions.getInt(1); int ayah = suggestions.getInt(2); String text = suggestions.getString(3); String foundText = context.getString( R.string.found_in_sura, QuranInfo.getSuraName(context, sura, false), ayah); gotResults = true; MatrixCursor.RowBuilder row = mc.newRow(); int id = suggestions.getInt(0); row.add(id); row.add(text); row.add(foundText); row.add(id); } while (suggestions.moveToNext()); } } finally { DatabaseUtils.closeCursor(suggestions); } } return mc; } private Cursor search(String query, List<LocalTranslation> translations) { Timber.d("query: %s", query); final Context context = getContext(); final boolean queryIsArabic = QuranUtils.doesStringContainArabic(query); final boolean haveArabic = queryIsArabic && QuranFileUtils.hasTranslation(context, QURAN_ARABIC_DATABASE); if (translations.size() == 0 || (queryIsArabic && !haveArabic)) { return null; } int start = haveArabic ? -1 : 0; int total = translations.size() + (haveArabic ? 1 : 0); List<Cursor> cursorList = new ArrayList<>(); for (int i = start; i < total; i++) { String databaseName; if (i < 0) { databaseName = QURAN_ARABIC_DATABASE; } else { LocalTranslation translation = translations.get(i); // skip non-arabic databases if the query is in arabic if (queryIsArabic && translation.languageCode != null && !"ar".equals(translation.languageCode)) { continue; } else if (!queryIsArabic && "ar".equals(translation.languageCode)) { // skip arabic databases when the query isn't arabic continue; } databaseName = translation.filename; } Cursor cursor = search(query, databaseName, true); if (cursor != null) { cursorList.add(cursor); } } Cursor[] cursors = new Cursor[cursorList.size()]; for (int i = 0, size = cursorList.size(); i < size; i++) { cursors[i] = cursorList.get(i); } return new MergeCursor(cursors); } private Cursor search(String query, String databaseName, boolean wantSnippets) { final DatabaseHandler handler = DatabaseHandler.getDatabaseHandler(getContext(), databaseName); return handler.search(query, wantSnippets); } @Override public String getType(@NonNull Uri uri) { switch (uriMatcher.match(uri)) { case SEARCH_VERSES: { return VERSES_MIME_TYPE; } case SEARCH_SUGGEST: { return SearchManager.SUGGEST_MIME_TYPE; } default: { throw new IllegalArgumentException("Unknown URL " + uri); } } } @Override public Uri insert(@NonNull Uri uri, ContentValues values) { throw new UnsupportedOperationException(); } @Override public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException(); } @Override public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException(); } }