1 /* 2 * Copyright (C) 2007 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 android.test; 18 19 import com.google.android.collect.Sets; 20 21 import android.content.Context; 22 import android.content.ContextWrapper; 23 import android.content.ContentProvider; 24 import android.database.DatabaseErrorHandler; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.os.FileUtils; 27 import android.util.Log; 28 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileNotFoundException; 32 import java.io.FileOutputStream; 33 import java.util.Set; 34 35 /** 36 * This is a class which delegates to the given context, but performs database 37 * and file operations with a renamed database/file name (prefixes default 38 * names with a given prefix). 39 */ 40 public class RenamingDelegatingContext extends ContextWrapper { 41 42 private Context mFileContext; 43 private String mFilePrefix = null; 44 private File mCacheDir; 45 private final Object mSync = new Object(); 46 47 private Set<String> mDatabaseNames = Sets.newHashSet(); 48 private Set<String> mFileNames = Sets.newHashSet(); 49 50 public static <T extends ContentProvider> T providerWithRenamedContext( 51 Class<T> contentProvider, Context c, String filePrefix) 52 throws IllegalAccessException, InstantiationException { 53 return providerWithRenamedContext(contentProvider, c, filePrefix, false); 54 } 55 56 public static <T extends ContentProvider> T providerWithRenamedContext( 57 Class<T> contentProvider, Context c, String filePrefix, 58 boolean allowAccessToExistingFilesAndDbs) 59 throws IllegalAccessException, InstantiationException { 60 Class<T> mProviderClass = contentProvider; 61 T mProvider = mProviderClass.newInstance(); 62 RenamingDelegatingContext mContext = new RenamingDelegatingContext(c, filePrefix); 63 if (allowAccessToExistingFilesAndDbs) { 64 mContext.makeExistingFilesAndDbsAccessible(); 65 } 66 mProvider.attachInfo(mContext, null); 67 return mProvider; 68 } 69 70 /** 71 * Makes accessible all files and databases whose names match the filePrefix that was passed to 72 * the constructor. Normally only files and databases that were created through this context are 73 * accessible. 74 */ 75 public void makeExistingFilesAndDbsAccessible() { 76 String[] databaseList = mFileContext.databaseList(); 77 for (String diskName : databaseList) { 78 if (shouldDiskNameBeVisible(diskName)) { 79 mDatabaseNames.add(publicNameFromDiskName(diskName)); 80 } 81 } 82 String[] fileList = mFileContext.fileList(); 83 for (String diskName : fileList) { 84 if (shouldDiskNameBeVisible(diskName)) { 85 mFileNames.add(publicNameFromDiskName(diskName)); 86 } 87 } 88 } 89 90 /** 91 * Returns if the given diskName starts with the given prefix or not. 92 * @param diskName name of the database/file. 93 */ 94 boolean shouldDiskNameBeVisible(String diskName) { 95 return diskName.startsWith(mFilePrefix); 96 } 97 98 /** 99 * Returns the public name (everything following the prefix) of the given diskName. 100 * @param diskName name of the database/file. 101 */ 102 String publicNameFromDiskName(String diskName) { 103 if (!shouldDiskNameBeVisible(diskName)) { 104 throw new IllegalArgumentException("disk file should not be visible: " + diskName); 105 } 106 return diskName.substring(mFilePrefix.length(), diskName.length()); 107 } 108 109 /** 110 * @param context : the context that will be delagated. 111 * @param filePrefix : a prefix with which database and file names will be 112 * prefixed. 113 */ 114 public RenamingDelegatingContext(Context context, String filePrefix) { 115 super(context); 116 mFileContext = context; 117 mFilePrefix = filePrefix; 118 } 119 120 /** 121 * @param context : the context that will be delagated. 122 * @param fileContext : the context that file and db methods will be delgated to 123 * @param filePrefix : a prefix with which database and file names will be 124 * prefixed. 125 */ 126 public RenamingDelegatingContext(Context context, Context fileContext, String filePrefix) { 127 super(context); 128 mFileContext = fileContext; 129 mFilePrefix = filePrefix; 130 } 131 132 public String getDatabasePrefix() { 133 return mFilePrefix; 134 } 135 136 private String renamedFileName(String name) { 137 return mFilePrefix + name; 138 } 139 140 @Override 141 public SQLiteDatabase openOrCreateDatabase(String name, 142 int mode, SQLiteDatabase.CursorFactory factory) { 143 final String internalName = renamedFileName(name); 144 if (!mDatabaseNames.contains(name)) { 145 mDatabaseNames.add(name); 146 mFileContext.deleteDatabase(internalName); 147 } 148 return mFileContext.openOrCreateDatabase(internalName, mode, factory); 149 } 150 151 @Override 152 public SQLiteDatabase openOrCreateDatabase(String name, 153 int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { 154 final String internalName = renamedFileName(name); 155 if (!mDatabaseNames.contains(name)) { 156 mDatabaseNames.add(name); 157 mFileContext.deleteDatabase(internalName); 158 } 159 return mFileContext.openOrCreateDatabase(internalName, mode, factory, errorHandler); 160 } 161 162 @Override 163 public boolean deleteDatabase(String name) { 164 if (mDatabaseNames.contains(name)) { 165 mDatabaseNames.remove(name); 166 return mFileContext.deleteDatabase(renamedFileName(name)); 167 } else { 168 return false; 169 } 170 } 171 172 @Override 173 public File getDatabasePath(String name) { 174 return mFileContext.getDatabasePath(renamedFileName(name)); 175 } 176 177 @Override 178 public String[] databaseList() { 179 return mDatabaseNames.toArray(new String[]{}); 180 } 181 182 @Override 183 public FileInputStream openFileInput(String name) 184 throws FileNotFoundException { 185 final String internalName = renamedFileName(name); 186 if (mFileNames.contains(name)) { 187 return mFileContext.openFileInput(internalName); 188 } else { 189 throw new FileNotFoundException(internalName); 190 } 191 } 192 193 @Override 194 public FileOutputStream openFileOutput(String name, int mode) 195 throws FileNotFoundException { 196 mFileNames.add(name); 197 return mFileContext.openFileOutput(renamedFileName(name), mode); 198 } 199 200 @Override 201 public File getFileStreamPath(String name) { 202 return mFileContext.getFileStreamPath(renamedFileName(name)); 203 } 204 205 @Override 206 public boolean deleteFile(String name) { 207 if (mFileNames.contains(name)) { 208 mFileNames.remove(name); 209 return mFileContext.deleteFile(renamedFileName(name)); 210 } else { 211 return false; 212 } 213 } 214 215 @Override 216 public String[] fileList() { 217 return mFileNames.toArray(new String[]{}); 218 } 219 220 /** 221 * In order to support calls to getCacheDir(), we create a temp cache dir (inside the real 222 * one) and return it instead. This code is basically getCacheDir(), except it uses the real 223 * cache dir as the parent directory and creates a test cache dir inside that. 224 */ 225 @Override 226 public File getCacheDir() { 227 synchronized (mSync) { 228 if (mCacheDir == null) { 229 mCacheDir = new File(mFileContext.getCacheDir(), renamedFileName("cache")); 230 } 231 if (!mCacheDir.exists()) { 232 if(!mCacheDir.mkdirs()) { 233 Log.w("RenamingDelegatingContext", "Unable to create cache directory"); 234 return null; 235 } 236 FileUtils.setPermissions( 237 mCacheDir.getPath(), 238 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, 239 -1, -1); 240 } 241 } 242 return mCacheDir; 243 } 244 245 246 // /** 247 // * Given an array of files returns only those whose names indicate that they belong to this 248 // * context. 249 // * @param allFiles the original list of files 250 // * @return the pruned list of files 251 // */ 252 // private String[] prunedFileList(String[] allFiles) { 253 // List<String> files = Lists.newArrayList(); 254 // for (String file : allFiles) { 255 // if (file.startsWith(mFilePrefix)) { 256 // files.add(file); 257 // } 258 // } 259 // return files.toArray(new String[]{}); 260 // } 261 } 262