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