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