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 com.android.providers.contacts; 18 19 import android.content.ContentProvider; 20 import android.content.Context; 21 import android.content.ContextWrapper; 22 import android.database.DatabaseErrorHandler; 23 import android.database.sqlite.SQLiteDatabase; 24 import android.os.FileUtils; 25 import android.util.Log; 26 27 import com.google.android.collect.Sets; 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 file was copied from framework/base. The DB related file names now understand fullpath 37 * filenames and will not append the prefix for them. 38 */ 39 public 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 providerWithRenamedContext( Class<T> contentProvider, Context c, String filePrefix)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 providerWithRenamedContext( Class<T> contentProvider, Context c, String filePrefix, boolean allowAccessToExistingFilesAndDbs)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.attachInfoForTesting(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 */ makeExistingFilesAndDbsAccessible()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 */ shouldDiskNameBeVisible(String diskName)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 */ publicNameFromDiskName(String diskName)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 delegated. 110 * @param filePrefix : a prefix with which database and file names will be 111 * prefixed. 112 */ RenamingDelegatingContext(Context context, String filePrefix)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 delegated. 121 * @param fileContext : the context that file and db methods will be delegated to 122 * @param filePrefix : a prefix with which database and file names will be 123 * prefixed. 124 */ RenamingDelegatingContext(Context context, Context fileContext, String filePrefix)125 public RenamingDelegatingContext(Context context, Context fileContext, String filePrefix) { 126 super(context); 127 mFileContext = fileContext; 128 mFilePrefix = filePrefix; 129 } 130 getDatabasePrefix()131 public String getDatabasePrefix() { 132 return mFilePrefix; 133 } 134 renamedFileName(String name)135 private String renamedFileName(String name) { 136 return mFilePrefix + name; 137 } 138 139 @Override openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory)140 public SQLiteDatabase openOrCreateDatabase(String name, 141 int mode, SQLiteDatabase.CursorFactory factory) { 142 if (name.startsWith("/")) { 143 return mFileContext.openOrCreateDatabase(name, mode, factory); 144 } 145 final String internalName = renamedFileName(name); 146 if (!mDatabaseNames.contains(name)) { 147 mDatabaseNames.add(name); 148 mFileContext.deleteDatabase(internalName); 149 } 150 return mFileContext.openOrCreateDatabase(internalName, mode, factory); 151 } 152 153 @Override openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler)154 public SQLiteDatabase openOrCreateDatabase(String name, 155 int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { 156 if (name.startsWith("/")) { 157 return mFileContext.openOrCreateDatabase(name, mode, factory, errorHandler); 158 } 159 final String internalName = renamedFileName(name); 160 if (!mDatabaseNames.contains(name)) { 161 mDatabaseNames.add(name); 162 mFileContext.deleteDatabase(internalName); 163 } 164 return mFileContext.openOrCreateDatabase(internalName, mode, factory, errorHandler); 165 } 166 167 @Override deleteDatabase(String name)168 public boolean deleteDatabase(String name) { 169 if (name.startsWith("/")) { 170 return mFileContext.deleteDatabase(name); 171 } 172 if (mDatabaseNames.contains(name)) { 173 mDatabaseNames.remove(name); 174 return mFileContext.deleteDatabase(renamedFileName(name)); 175 } else { 176 return false; 177 } 178 } 179 180 @Override getDatabasePath(String name)181 public File getDatabasePath(String name) { 182 if (name.startsWith("/")) { 183 return mFileContext.getDatabasePath(name); 184 } 185 return mFileContext.getDatabasePath(renamedFileName(name)); 186 } 187 188 @Override databaseList()189 public String[] databaseList() { 190 return mDatabaseNames.toArray(new String[]{}); 191 } 192 193 @Override openFileInput(String name)194 public FileInputStream openFileInput(String name) 195 throws FileNotFoundException { 196 final String internalName = renamedFileName(name); 197 if (mFileNames.contains(name)) { 198 return mFileContext.openFileInput(internalName); 199 } else { 200 throw new FileNotFoundException(internalName); 201 } 202 } 203 204 @Override openFileOutput(String name, int mode)205 public FileOutputStream openFileOutput(String name, int mode) 206 throws FileNotFoundException { 207 mFileNames.add(name); 208 return mFileContext.openFileOutput(renamedFileName(name), mode); 209 } 210 211 @Override getFileStreamPath(String name)212 public File getFileStreamPath(String name) { 213 return mFileContext.getFileStreamPath(renamedFileName(name)); 214 } 215 216 @Override deleteFile(String name)217 public boolean deleteFile(String name) { 218 if (mFileNames.contains(name)) { 219 mFileNames.remove(name); 220 return mFileContext.deleteFile(renamedFileName(name)); 221 } else { 222 return false; 223 } 224 } 225 226 @Override fileList()227 public String[] fileList() { 228 return mFileNames.toArray(new String[]{}); 229 } 230 231 /** 232 * In order to support calls to getCacheDir(), we create a temp cache dir (inside the real 233 * one) and return it instead. This code is basically getCacheDir(), except it uses the real 234 * cache dir as the parent directory and creates a test cache dir inside that. 235 */ 236 @Override getCacheDir()237 public File getCacheDir() { 238 synchronized (mSync) { 239 if (mCacheDir == null) { 240 mCacheDir = new File(mFileContext.getCacheDir(), renamedFileName("cache")); 241 } 242 if (!mCacheDir.exists()) { 243 if(!mCacheDir.mkdirs()) { 244 Log.w("RenamingDelegatingContext", "Unable to create cache directory"); 245 return null; 246 } 247 FileUtils.setPermissions( 248 mCacheDir.getPath(), 249 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, 250 -1, -1); 251 } 252 } 253 return mCacheDir; 254 } 255 } 256