/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.cbaines.suma; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.provider.BaseColumns; import android.util.Log; import com.j256.ormlite.android.AndroidCompiledStatement; import com.j256.ormlite.android.apptools.OpenHelperManager; import com.j256.ormlite.dao.Dao; import com.j256.ormlite.stmt.PreparedQuery; import com.j256.ormlite.stmt.QueryBuilder; import com.j256.ormlite.stmt.StatementBuilder.StatementType; /** * Provides access to the dictionary database. */ public class MapContentProvider extends ContentProvider { String TAG = "MapContentProvider"; public static String AUTHORITY = "net.cbaines.suma.provider"; // public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY // + "/building"); // MIME types used for searching words or looking up a single definition public static final String ALLS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.net.cbaines.suma.provider.all"; public static final String ALL_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.net.cbaines.suma.provider.all"; public static final String BUILDINGS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.net.cbaines.suma.provider.building"; public static final String BUILDING_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.net.cbaines.suma.provider.building"; public static final String BUS_STOPS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.net.cbaines.suma.provider.bus-stop"; public static final String BUS_STOP_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.net.cbaines.suma.provider.bus-stop"; public static final String SITES_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.net.cbaines.suma.provider.site"; public static final String SITE_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.net.cbaines.suma.provider.site"; public static final String BUSES_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.net.cbaines.suma.provider.bus"; public static final String BUS_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.net.cbaines.suma.provider.bus"; private DatabaseHelper helper; // UriMatcher stuff private static final int SEARCH_ALL = 0; private static final int GET_ALL = 1; private static final int SEARCH_BUILDINGS = 2; private static final int GET_BUILDING = 3; private static final int SEARCH_BUS_STOPS = 4; private static final int GET_BUS_STOP = 5; private static final int SEARCH_SITES = 6; private static final int GET_SITE = 7; private static final int SEARCH_BUSES = 8; private static final int GET_BUS = 9; private static final int SEARCH_SUGGEST = 10; private static final int REFRESH_SHORTCUT = 11; private static final UriMatcher sURIMatcher = buildUriMatcher(); /** * Builds up a UriMatcher for search suggestion and shortcut refresh queries. */ private static UriMatcher buildUriMatcher() { UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); // to get definitions... matcher.addURI(AUTHORITY, "all", SEARCH_BUILDINGS); matcher.addURI(AUTHORITY, "all/*", GET_BUILDING); matcher.addURI(AUTHORITY, "building", SEARCH_BUILDINGS); matcher.addURI(AUTHORITY, "building/*", GET_BUILDING); matcher.addURI(AUTHORITY, "bus-stop", SEARCH_BUS_STOPS); matcher.addURI(AUTHORITY, "bus-stop/*", GET_BUS_STOP); matcher.addURI(AUTHORITY, "site", SEARCH_SITES); matcher.addURI(AUTHORITY, "site/*", GET_SITE); matcher.addURI(AUTHORITY, "bus", SEARCH_BUSES); matcher.addURI(AUTHORITY, "bus/*", GET_BUS); // to get suggestions... matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST); matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST); /* * The following are unused in this implementation, but if we include {@link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as * a column in our suggestions table, we could expect to receive refresh queries when a shortcutted suggestion is * displayed in Quick Search Box, in which case, the following Uris would be provided and we would return a cursor with a * single item representing the refreshed suggestion data. */ matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, REFRESH_SHORTCUT); matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", REFRESH_SHORTCUT); return matcher; } @Override public boolean onCreate() { helper = OpenHelperManager.getHelper(this.getContext(), DatabaseHelper.class); return true; } /** * Handles all the dictionary searches and suggestion queries from the Search Manager. When requesting a specific word, the * uri alone is required. When searching all of the dictionary for matches, the selectionArgs argument must carry the search * query as the first element. All other arguments are ignored. */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Use the UriMatcher to see what kind of query we have and format the // db query accordingly switch (sURIMatcher.match(uri)) { case SEARCH_SUGGEST: if (selectionArgs == null) { throw new IllegalArgumentException("selectionArgs must be provided for the Uri: " + uri); } try { return getSuggestions(selectionArgs[0]); } catch (SQLException e1) { e1.printStackTrace(); } case SEARCH_ALL: if (selectionArgs == null) { throw new IllegalArgumentException("selectionArgs must be provided for the Uri: " + uri); } try { return searchAll(selectionArgs[0]); } catch (SQLException e) { e.printStackTrace(); } case GET_ALL: try { return getAll(uri); } catch (SQLException e) { e.printStackTrace(); } case SEARCH_BUILDINGS: if (selectionArgs == null) { throw new IllegalArgumentException("selectionArgs must be provided for the Uri: " + uri); } try { return searchBuildings(selectionArgs[0]); } catch (SQLException e) { e.printStackTrace(); } case GET_BUILDING: try { return getBuilding(uri); } catch (SQLException e) { e.printStackTrace(); } case REFRESH_SHORTCUT: try { return refreshShortcut(uri); } catch (SQLException e) { e.printStackTrace(); } default: throw new IllegalArgumentException("Unknown Uri: " + uri); } } private Cursor getSuggestions(String query) throws SQLException { Log.v(TAG, "Got query for " + query); String[] columnNames = { BaseColumns._ID, SearchManager.SUGGEST_COLUMN_ICON_1, SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_TEXT_2, SearchManager.SUGGEST_COLUMN_INTENT_DATA }; List results = new ArrayList(); MatrixCursor cursor = new MatrixCursor(columnNames); int id = 0; Dao buildingDao = helper.getBuildingDao(); QueryBuilder qb = buildingDao.queryBuilder(); qb.where().like(Building.ID_FIELD_NAME, "%" + query + "%").or().like(Building.NAME_FIELD_NAME, "%" + query + "%"); PreparedQuery preparedQuery = qb.prepare(); results.addAll(buildingDao.query(preparedQuery)); Dao busStopDao = helper.getBusStopDao(); QueryBuilder busStopQB = busStopDao.queryBuilder(); busStopQB.where().like(BusStop.ID_FIELD_NAME, "%" + query + "%").or() .like(BusStop.DESCRIPTION_FIELD_NAME, "%" + query + "%"); PreparedQuery busStopPreparedQuery = busStopQB.prepare(); results.addAll(busStopDao.query(busStopPreparedQuery)); Dao siteDao = helper.getSiteDao(); QueryBuilder siteQB = siteDao.queryBuilder(); siteQB.where().like(Site.NAME_FIELD_NAME, "%" + query + "%").or().like(Site.ID_FIELD_NAME, "%" + query + "%"); PreparedQuery sitePreparedQuery = siteQB.prepare(); results.addAll(siteDao.query(sitePreparedQuery)); Collections.sort(results, new StringPOIDistanceComparator(query)); for (POI poi : results) { if (poi instanceof Site) { Site site = (Site) poi; Object[] values = { id++, R.drawable.empty, site.name, site.id, "geo:" + Util.E6IntToDouble(site.point.getLatitudeE6()) + "," + Util.E6IntToDouble(site.point.getLongitudeE6()) + "?z=18" }; cursor.addRow(values); } else if (poi instanceof Building) { Building building = (Building) poi; Object[] values = { id++, R.drawable.building, building.name, building.id, "geo:" + Util.E6IntToDouble(building.point.getLatitudeE6()) + "," + Util.E6IntToDouble(building.point.getLongitudeE6()) + "?z=18" }; cursor.addRow(values); } else if (poi instanceof BusStop) { BusStop busStop = (BusStop) poi; Object[] values = { id++, R.drawable.busstop, busStop.description, busStop.id, "geo:" + Util.E6IntToDouble(busStop.point.getLatitudeE6()) + "," + Util.E6IntToDouble(busStop.point.getLongitudeE6()) + "?z=18" }; cursor.addRow(values); } else { Log.e(TAG, "Error, unexpected class"); } } return cursor; } private Cursor searchBuildings(String query) throws SQLException { Dao buildingDao = helper.getBuildingDao(); QueryBuilder qb = buildingDao.queryBuilder(); qb.where().eq(Building.ID_FIELD_NAME, "%" + query + "%").or().eq(Building.NAME_FIELD_NAME, "%" + query + "%"); PreparedQuery preparedQuery = qb.prepare(); AndroidCompiledStatement compiledStatement = (AndroidCompiledStatement) preparedQuery.compile(helper .getConnectionSource().getReadOnlyConnection(), StatementType.SELECT); Cursor cursor = compiledStatement.getCursor(); return cursor; } private Cursor getBuilding(Uri uri) throws SQLException { String buildingID = uri.getLastPathSegment(); Dao buildingDao = helper.getBuildingDao(); QueryBuilder qb = buildingDao.queryBuilder(); qb.where().eq(Building.ID_FIELD_NAME, buildingID); PreparedQuery preparedQuery = qb.prepare(); AndroidCompiledStatement compiledStatement = (AndroidCompiledStatement) preparedQuery.compile(helper .getConnectionSource().getReadOnlyConnection(), StatementType.SELECT); Cursor cursor = compiledStatement.getCursor(); return cursor; } private Cursor searchAll(String query) throws SQLException { Dao buildingDao = helper.getBuildingDao(); QueryBuilder qb = buildingDao.queryBuilder(); qb.where().eq(Building.ID_FIELD_NAME, "%" + query + "%").or().eq(Building.NAME_FIELD_NAME, "%" + query + "%"); PreparedQuery preparedQuery = qb.prepare(); AndroidCompiledStatement compiledStatement = (AndroidCompiledStatement) preparedQuery.compile(helper .getConnectionSource().getReadOnlyConnection(), StatementType.SELECT); Cursor cursor = compiledStatement.getCursor(); return cursor; } private Cursor getAll(Uri uri) throws SQLException { String buildingID = uri.getLastPathSegment(); Dao buildingDao = helper.getBuildingDao(); QueryBuilder qb = buildingDao.queryBuilder(); qb.where().eq(Building.ID_FIELD_NAME, buildingID); PreparedQuery preparedQuery = qb.prepare(); AndroidCompiledStatement compiledStatement = (AndroidCompiledStatement) preparedQuery.compile(helper .getConnectionSource().getReadOnlyConnection(), StatementType.SELECT); Cursor cursor = compiledStatement.getCursor(); return cursor; } private Cursor refreshShortcut(Uri uri) throws SQLException { /* * This won't be called with the current implementation, but if we include {@link * SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our suggestions table, we could expect to receive refresh * queries when a shortcutted suggestion is displayed in Quick Search Box. In which case, this method will query the table * for the specific word, using the given item Uri and provide all the columns originally provided with the suggestion * query. */ String buildingID = uri.getLastPathSegment(); Dao buildingDao = helper.getBuildingDao(); QueryBuilder qb = buildingDao.queryBuilder(); qb.where().eq(Building.ID_FIELD_NAME, buildingID); PreparedQuery preparedQuery = qb.prepare(); AndroidCompiledStatement compiledStatement = (AndroidCompiledStatement) preparedQuery.compile(helper .getConnectionSource().getReadOnlyConnection(), StatementType.SELECT); Cursor cursor = compiledStatement.getCursor(); return cursor; } /** * This method is required in order to query the supported types. It's also useful in our own query() method to determine the * type of Uri received. */ @Override public String getType(Uri uri) { switch (sURIMatcher.match(uri)) { case SEARCH_ALL: return ALLS_MIME_TYPE; case GET_ALL: return ALL_MIME_TYPE; case SEARCH_BUILDINGS: return BUILDINGS_MIME_TYPE; case GET_BUILDING: return BUILDING_MIME_TYPE; case SEARCH_BUS_STOPS: return BUS_STOPS_MIME_TYPE; case GET_BUS_STOP: return BUS_STOP_MIME_TYPE; case SEARCH_SITES: return SITES_MIME_TYPE; case GET_SITE: return SITE_MIME_TYPE; case SEARCH_BUSES: return BUSES_MIME_TYPE; case GET_BUS: return BUS_MIME_TYPE; case SEARCH_SUGGEST: return SearchManager.SUGGEST_MIME_TYPE; case REFRESH_SHORTCUT: return SearchManager.SHORTCUT_MIME_TYPE; default: throw new IllegalArgumentException("Unknown URL " + uri); } } // Other required implementations... @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException(); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException(); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException(); } }