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.content.pm.ProviderInfo; 25 import android.database.Cursor; 26 import android.database.MatrixCursor; 27 import android.net.Uri; 28 29 import com.android.mail.utils.MatrixCursorWithCachedColumns; 30 31 import java.util.ArrayList; 32 import java.util.HashMap; 33 import java.util.Map.Entry; 34 import java.util.Set; 35 36 /** 37 * TestProvider is a ContentProvider that can be used to simulate the storage and retrieval of 38 * rows from any ContentProvider, even if that provider does not exist in the caller's package. 39 * 40 * It is specifically designed to enable testing of sync adapters that create 41 * ContentProviderOperations (CPOs) that are then executed using ContentResolver.applyBatch(). 42 * Why is this useful? Because we can't instantiate CalendarProvider or ContactsProvider from our 43 * package, as required by MockContentResolver.addProvider() 44 * 45 * Usage: 46 * 47 * ContentResolver.applyBatch(MockProvider.AUTHORITY, batch) will cause the CPOs to be executed, 48 * returning an array of ContentProviderResult; in the case of inserts, the result will include 49 * a Uri that can be used via query(). Note that the CPOs can contain references to any authority. 50 * 51 * query() does not allow non-null selection, selectionArgs, or sortOrder arguments; the 52 * presence of these will result in an UnsupportedOperationException insert() acts as expected, 53 * returning a Uri that can be directly used in a query 54 * 55 * delete() and update() do not allow non-null selection or selectionArgs arguments; the presence 56 * of these will result in an UnsupportedOperationException 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 perform 60 * 61 * NOTE: See MockProviderTests for usage examples 62 **/ 63 public class TestProvider extends ContentProvider { 64 public static final String AUTHORITY = "com.android.mail.mock.provider"; 65 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 TestProvider() { 74 super(); 75 } 76 77 public TestProvider(Context context) { 78 this(); 79 ProviderInfo info = new ProviderInfo(); 80 info.authority = AUTHORITY; 81 attachInfo(context, info); 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 @Override 90 public int delete(Uri uri, String selection, String[] selectionArgs) { 91 if (selection != null || selectionArgs != null) { 92 throw new UnsupportedOperationException(); 93 } 94 String path = uri.getPath(); 95 if (mMockStore.containsKey(path)) { 96 mMockStore.remove(path); 97 return 1; 98 } else { 99 return 0; 100 } 101 } 102 103 @Override 104 public String getType(Uri uri) { 105 throw new UnsupportedOperationException(); 106 } 107 108 @Override 109 public Uri insert(Uri uri, ContentValues values) { 110 // Remove the leading slash 111 String table = uri.getPath().substring(1); 112 long id = mMockId++; 113 Uri newUri = new Uri.Builder().scheme("content").authority(AUTHORITY).path(table) 114 .appendPath(Long.toString(id)).build(); 115 // Remember to store the _id 116 values.put(ID_COLUMN, id); 117 mMockStore.put(newUri.getPath(), values); 118 int match = sURIMatcher.match(uri); 119 if (match == UriMatcher.NO_MATCH) { 120 sURIMatcher.addURI(AUTHORITY, table, TABLE); 121 sURIMatcher.addURI(AUTHORITY, table + "/#", RECORD); 122 } 123 return newUri; 124 } 125 126 @Override 127 public boolean onCreate() { 128 return false; 129 } 130 131 @Override 132 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 133 String sortOrder) { 134 if (selection != null || selectionArgs != null || sortOrder != null || projection == null) { 135 throw new UnsupportedOperationException(); 136 } 137 final int match = sURIMatcher.match(uri); 138 ArrayList<ContentValues> valuesList = new ArrayList<ContentValues>(); 139 switch (match) { 140 case TABLE: 141 Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet(); 142 String prefix = uri.getPath() + "/"; 143 for (Entry<String, ContentValues> entry : entrySet) { 144 if (entry.getKey().startsWith(prefix)) { 145 valuesList.add(entry.getValue()); 146 } 147 } 148 break; 149 case RECORD: 150 ContentValues values = mMockStore.get(uri.getPath()); 151 if (values != null) { 152 valuesList.add(values); 153 } 154 break; 155 default: 156 throw new IllegalArgumentException("Unknown URI " + uri); 157 } 158 MatrixCursor cursor = new MatrixCursorWithCachedColumns(projection, 1); 159 for (ContentValues cv : valuesList) { 160 Object[] rowValues = new Object[projection.length]; 161 int i = 0; 162 for (String column : projection) { 163 rowValues[i++] = cv.get(column); 164 } 165 cursor.addRow(rowValues); 166 } 167 return cursor; 168 } 169 170 @Override 171 public int update(Uri uri, ContentValues newValues, String selection, String[] selectionArgs) { 172 if (selection != null || selectionArgs != null) { 173 throw new UnsupportedOperationException(); 174 } 175 final int match = sURIMatcher.match(uri); 176 ArrayList<ContentValues> updateValuesList = new ArrayList<ContentValues>(); 177 String path = uri.getPath(); 178 switch (match) { 179 case TABLE: 180 Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet(); 181 String prefix = path + "/"; 182 for (Entry<String, ContentValues> entry : entrySet) { 183 if (entry.getKey().startsWith(prefix)) { 184 updateValuesList.add(entry.getValue()); 185 } 186 } 187 break; 188 case RECORD: 189 ContentValues cv = mMockStore.get(path); 190 if (cv != null) { 191 updateValuesList.add(cv); 192 } 193 break; 194 default: 195 throw new IllegalArgumentException("Unknown URI " + uri); 196 } 197 Set<Entry<String, Object>> newValuesSet = newValues.valueSet(); 198 for (Entry<String, Object> entry : newValuesSet) { 199 String key = entry.getKey(); 200 Object value = entry.getValue(); 201 for (ContentValues targetValues : updateValuesList) { 202 if (value instanceof Integer) { 203 targetValues.put(key, (Integer) value); 204 } else if (value instanceof Long) { 205 targetValues.put(key, (Long) value); 206 } else if (value instanceof String) { 207 targetValues.put(key, (String) value); 208 } else if (value instanceof Boolean) { 209 targetValues.put(key, (Boolean) value); 210 } else { 211 throw new IllegalArgumentException(); 212 } 213 } 214 } 215 for (ContentValues targetValues : updateValuesList) { 216 switch(match) { 217 case TABLE: 218 mMockStore.put(path + "/" + targetValues.getAsLong(ID_COLUMN), targetValues); 219 break; 220 case RECORD: 221 mMockStore.put(path, targetValues); 222 break; 223 } 224 } 225 return updateValuesList.size(); 226 } 227 } 228