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