1 /******************************************************************************* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 *******************************************************************************/ 17 18 package com.android.mail.browse; 19 20 import android.content.ContentProvider; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.UriMatcher; 24 import android.database.Cursor; 25 import android.database.MatrixCursor; 26 import android.net.Uri; 27 28 import com.android.mail.utils.MatrixCursorWithCachedColumns; 29 30 import java.util.ArrayList; 31 import java.util.HashMap; 32 import java.util.Map.Entry; 33 import java.util.Set; 34 35 /** 36 * TestProvider is a ContentProvider that can be used to simulate the storage and retrieval of 37 * rows from any ContentProvider, even if that provider does not exist in the caller's package. 38 * 39 * It is specifically designed to enable testing of sync adapters that create 40 * ContentProviderOperations (CPOs) that are then executed using ContentResolver.applyBatch(). 41 * Why is this useful? Because we can't instantiate CalendarProvider or ContactsProvider from our 42 * package, as required by MockContentResolver.addProvider() 43 * 44 * Usage: 45 * 46 * ContentResolver.applyBatch(MockProvider.AUTHORITY, batch) will cause the CPOs to be executed, 47 * returning an array of ContentProviderResult; in the case of inserts, the result will include 48 * a Uri that can be used via query(). Note that the CPOs can contain references to any authority. 49 * 50 * query() does not allow non-null selection, selectionArgs, or sortOrder arguments; the 51 * presence of these will result in an UnsupportedOperationException insert() acts as expected, 52 * returning a Uri that can be directly used in a query 53 * 54 * delete() and update() do not allow non-null selection or selectionArgs arguments; the presence 55 * of these will result in an UnsupportedOperationException 56 * 57 * NOTE: When using any operation other than applyBatch, the Uri to be used must be created with 58 * MockProvider.uri(yourUri). This guarantees that the operation is sent to MockProvider 59 * 60 * NOTE: MockProvider only simulates direct storage/retrieval of rows; it does not (and can not) 61 * simulate other actions (e.g. creation of ancillary data) that the actual provider might perform 62 * 63 * NOTE: See MockProviderTests for usage examples 64 **/ 65 public class TestProvider extends ContentProvider { 66 public static final String AUTHORITY = "com.android.mail.mock.provider"; 67 /* package */static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 68 69 /* package */static final int TABLE = 100; 70 /* package */static final int RECORD = 101; 71 72 public static final String ID_COLUMN = "_id"; 73 74 public TestProvider() { 75 super(); 76 } 77 78 public TestProvider(Context context) { 79 this(); 80 attachInfo(context, null); 81 } 82 83 // We'll store our values here 84 private HashMap<String, ContentValues> mMockStore = new HashMap<String, ContentValues>(); 85 // And we'll generate new id's from here 86 long mMockId = 1; 87 88 /** 89 * Create a Uri for MockProvider from a given Uri 90 * 91 * @param uri the Uri from which the MockProvider Uri will be created 92 * @return a Uri that can be used with MockProvider 93 */ 94 public static Uri uri(Uri uri) { 95 return new Uri.Builder().scheme("content").authority(AUTHORITY) 96 .path(uri.getPath().substring(1)).build(); 97 } 98 99 @Override 100 public int delete(Uri uri, String selection, String[] selectionArgs) { 101 if (selection != null || selectionArgs != null) { 102 throw new UnsupportedOperationException(); 103 } 104 String path = uri.getPath(); 105 if (mMockStore.containsKey(path)) { 106 mMockStore.remove(path); 107 return 1; 108 } else { 109 return 0; 110 } 111 } 112 113 @Override 114 public String getType(Uri uri) { 115 throw new UnsupportedOperationException(); 116 } 117 118 @Override 119 public Uri insert(Uri uri, ContentValues values) { 120 // Remove the leading slash 121 String table = uri.getPath().substring(1); 122 long id = mMockId++; 123 Uri newUri = new Uri.Builder().scheme("content").authority(AUTHORITY).path(table) 124 .appendPath(Long.toString(id)).build(); 125 // Remember to store the _id 126 values.put(ID_COLUMN, id); 127 mMockStore.put(newUri.getPath(), values); 128 int match = sURIMatcher.match(uri); 129 if (match == UriMatcher.NO_MATCH) { 130 sURIMatcher.addURI(AUTHORITY, table, TABLE); 131 sURIMatcher.addURI(AUTHORITY, table + "/#", RECORD); 132 } 133 return newUri; 134 } 135 136 @Override 137 public boolean onCreate() { 138 return false; 139 } 140 141 @Override 142 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 143 String sortOrder) { 144 if (selection != null || selectionArgs != null || sortOrder != null || projection == null) { 145 throw new UnsupportedOperationException(); 146 } 147 final int match = sURIMatcher.match(uri(uri)); 148 ArrayList<ContentValues> valuesList = new ArrayList<ContentValues>(); 149 switch (match) { 150 case TABLE: 151 Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet(); 152 String prefix = uri.getPath() + "/"; 153 for (Entry<String, ContentValues> entry : entrySet) { 154 if (entry.getKey().startsWith(prefix)) { 155 valuesList.add(entry.getValue()); 156 } 157 } 158 break; 159 case RECORD: 160 ContentValues values = mMockStore.get(uri.getPath()); 161 if (values != null) { 162 valuesList.add(values); 163 } 164 break; 165 default: 166 throw new IllegalArgumentException("Unknown URI " + uri); 167 } 168 MatrixCursor cursor = new MatrixCursorWithCachedColumns(projection, 1); 169 for (ContentValues cv : valuesList) { 170 Object[] rowValues = new Object[projection.length]; 171 int i = 0; 172 for (String column : projection) { 173 rowValues[i++] = cv.get(column); 174 } 175 cursor.addRow(rowValues); 176 } 177 return cursor; 178 } 179 180 @Override 181 public int update(Uri uri, ContentValues newValues, String selection, String[] selectionArgs) { 182 if (selection != null || selectionArgs != null) { 183 throw new UnsupportedOperationException(); 184 } 185 final int match = sURIMatcher.match(uri(uri)); 186 ArrayList<ContentValues> updateValuesList = new ArrayList<ContentValues>(); 187 String path = uri.getPath(); 188 switch (match) { 189 case TABLE: 190 Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet(); 191 String prefix = path + "/"; 192 for (Entry<String, ContentValues> entry : entrySet) { 193 if (entry.getKey().startsWith(prefix)) { 194 updateValuesList.add(entry.getValue()); 195 } 196 } 197 break; 198 case RECORD: 199 ContentValues cv = mMockStore.get(path); 200 if (cv != null) { 201 updateValuesList.add(cv); 202 } 203 break; 204 default: 205 throw new IllegalArgumentException("Unknown URI " + uri); 206 } 207 Set<Entry<String, Object>> newValuesSet = newValues.valueSet(); 208 for (Entry<String, Object> entry : newValuesSet) { 209 String key = entry.getKey(); 210 Object value = entry.getValue(); 211 for (ContentValues targetValues : updateValuesList) { 212 if (value instanceof Integer) { 213 targetValues.put(key, (Integer) value); 214 } else if (value instanceof Long) { 215 targetValues.put(key, (Long) value); 216 } else if (value instanceof String) { 217 targetValues.put(key, (String) value); 218 } else if (value instanceof Boolean) { 219 targetValues.put(key, (Boolean) value); 220 } else { 221 throw new IllegalArgumentException(); 222 } 223 } 224 } 225 for (ContentValues targetValues : updateValuesList) { 226 switch(match) { 227 case TABLE: 228 mMockStore.put(path + "/" + targetValues.getAsLong(ID_COLUMN), targetValues); 229 break; 230 case RECORD: 231 mMockStore.put(path, targetValues); 232 break; 233 } 234 } 235 return updateValuesList.size(); 236 } 237 } 238