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 65 // The constructor call below is commented out because it won't compile under SDK 66 67 public class MockProvider extends ContentProvider { 68 public static final String AUTHORITY = "com.android.exchange.mock.provider"; 69 /*package*/ static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 70 71 /*package*/ static final int TABLE = 100; 72 /*package*/ static final int RECORD = 101; 73 74 public static final String ID_COLUMN = "_id"; 75 76 // public MockProvider(Context context) { 77 // super(context, null, null, null); 78 // } 79 MockProvider()80 public MockProvider() { 81 super(); 82 } 83 84 // We'll store our values here 85 private HashMap<String, ContentValues> mMockStore = new HashMap<String, ContentValues>(); 86 // And we'll generate new id's from here 87 long mMockId = 1; 88 89 /** 90 * Create a Uri for MockProvider from a given Uri 91 * @param uri the Uri from which the MockProvider Uri will be created 92 * @return a Uri that can be used with MockProvider 93 */ uri(Uri uri)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 delete(Uri uri, String selection, String[] selectionArgs)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 getType(Uri uri)114 public String getType(Uri uri) { 115 throw new UnsupportedOperationException(); 116 } 117 118 @Override insert(Uri uri, ContentValues values)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 onCreate()137 public boolean onCreate() { 138 return false; 139 } 140 141 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)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 MatrixCursor(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 update(Uri uri, ContentValues newValues, String selection, String[] selectionArgs)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 mMockStore.put(path + "/" + targetValues.getAsLong(ID_COLUMN), targetValues); 227 } 228 return updateValuesList.size(); 229 } 230 } 231