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