package org.bbs.apklauncher;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.os.Build;
import android.util.Log;
/**
* control target context's file related stuff.
*
*/
abstract public class FileContext extends
ContextWrapper
//ContextThemeWrapper
{
private static final String TAG = FileContext.class.getSimpleName();
// file related method.
private static final String[] EMPTY_FILE_LIST = {};
public static /*final*/ boolean ENBABLE_FILE = ApkLauncherConfig.DEBUG && true;
private static final boolean DEBUG_FILE = ENBABLE_FILE && true;
private Object mSync = new Object();
private File mPreferencesDir;
private File mFilesDir;
private File mCacheDir;
private File mCodeCacheDir;
private File mDatabasesDir;
private Map<String, HashMap<String, Object>> sSharedPrefs;
abstract public String getTargetPackageName();
public FileContext(Context base) {
super(base);
}
// the root dir fot all file.
private File getDataDirFile(){
return ApkPackageManager.getInstance().getAppDataDir(getTargetPackageName());
}
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
if (!ENBABLE_FILE) {
return super.getSharedPreferences(name, mode);
}
Object sp;
synchronized (FileContext.class) {
if (sSharedPrefs == null) {
sSharedPrefs = new HashMap<String, HashMap<String, Object>>();
}
final String packageName = getPackageName();
HashMap<String, Object> packagePrefs = sSharedPrefs.get(packageName);
if (packagePrefs == null) {
packagePrefs = new HashMap<String, Object>();
sSharedPrefs.put(packageName, packagePrefs);
}
// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
if (getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
sp = packagePrefs.get(name);
if (sp == null) {
File prefsFile = getSharedPrefsFile(name);
sp = SharedPrefImplCompat.newObject(prefsFile, mode);
packagePrefs.put(name, sp);
if (DEBUG_FILE){
Log.d(TAG, "SharedPreferences(). name: " + name + " pref: " + sp);
}
return (SharedPreferences) sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// If somebody else (some other process) changed the prefs
// file behind our back, we reload it. This has been the
// historical (if undocumented) behavior.
SharedPrefImplCompat.startReloadIfChangedUnexpectedly(sp);
}
if (DEBUG_FILE){
Log.d(TAG, "SharedPreferences(). name: " + name + " pref: " + sp);
}
return (SharedPreferences) sp;
}
public File getSharedPrefsFile(String name) {
File file = makeFilename(getPreferencesDir(), name + ".xml");
if (DEBUG_FILE){
Log.d(TAG, "getSharedPrefsFile. name: " + name + " file: " + file);
}
return file;
}
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
File file = new File(base, name);
if (DEBUG_FILE){
Log.d(TAG, "makeFilename. base: " + base + " name: " + name + " file: " + file);
}
return file;
}
throw new IllegalArgumentException(
"File " + name + " contains a path separator");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
}
if (DEBUG_FILE){
Log.d(TAG, "getPreferencesDir. file: " + mPreferencesDir);
}
return mPreferencesDir;
}
}
@Override
public File getDir(String name, int mode) {
if (!ENBABLE_FILE) {
return super.getDir(name, mode);
}
name = "app_" + name;
File file = makeFilename(getDataDirFile(), name);
if (!file.exists()) {
file.mkdir();
// setFilePermissionsFromMode(file.getPath(), mode,
// FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH);
}
if (DEBUG_FILE){
Log.d(TAG, "getDir. name: " + name + " mode: " + mode + " file: " + file);
}
return file;
}
@Override
public File getFilesDir() {
if (!ENBABLE_FILE) {
return super.getFilesDir();
}
synchronized (mSync) {
if (mFilesDir == null) {
mFilesDir = new File(getDataDirFile(), "files");
}
if (DEBUG_FILE){
Log.d(TAG, "getFilesDir dir: " + mFilesDir);
}
return createFilesDirLocked(mFilesDir);
}
}
// Common-path handling of app data dir creation
private static File createFilesDirLocked(File file) {
if (!file.exists()) {
if (!file.mkdirs()) {
if (file.exists()) {
// spurious failure; probably racing with another process for this app
return file;
}
Log.w(TAG, "Unable to create files subdir " + file.getPath());
return null;
}
// FileUtils.setPermissions(
// file.getPath(),
// FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
// -1, -1);
}
return file;
}
@Override
public File getFileStreamPath(String name) {
if (!ENBABLE_FILE) {
return super.getFileStreamPath(name);
}
return makeFilename(getFilesDir(), name);
}
@Override
public String[] fileList() {
if (!ENBABLE_FILE) {
return super.fileList();
}
final String[] list = getFilesDir().list();
return (list != null) ? list : EMPTY_FILE_LIST;
}
@Override
public File getCacheDir() {
if (!ENBABLE_FILE) {
return super.getCacheDir();
}
synchronized (mSync) {
if (mCacheDir == null) {
mCacheDir = new File(getDataDirFile(), "cache");
}
if (DEBUG_FILE) {
Log.d(TAG, "getCacheDir. file: " + mCacheDir);
}
return createFilesDirLocked(mCacheDir);
}
}
@SuppressLint("NewApi")
@Override
public File getCodeCacheDir() {
if (!ENBABLE_FILE) {
return super.getCodeCacheDir();
}
synchronized (mSync) {
if (mCodeCacheDir == null) {
mCodeCacheDir = new File(getDataDirFile(), "code_cache");
}
if (DEBUG_FILE) {
Log.d(TAG, "getCodeCacheDir. file: " + mCodeCacheDir);
}
return createFilesDirLocked(mCodeCacheDir);
}
}
//// external file
// @Override
// public File getExternalCacheDir() {
// // Operates on primary external storage
// return getExternalCacheDirs()[0];
// }
//
// @Override
// public File[] getExternalCacheDirs() {
// synchronized (mSync) {
// if (mExternalCacheDirs == null) {
// mExternalCacheDirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
// }
//
// // Create dirs if needed
// return ensureDirsExistOrFilter(mExternalCacheDirs);
// }
// }
//
// @Override
// public File[] getExternalMediaDirs() {
// synchronized (mSync) {
// if (mExternalMediaDirs == null) {
// mExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
// }
//
// // Create dirs if needed
// return ensureDirsExistOrFilter(mExternalMediaDirs);
// }
// }
@Override
public String[] databaseList() {
if (!ENBABLE_FILE) {
return super.databaseList();
}
final String[] list = getDatabasesDir().list();
return (list != null) ? list : EMPTY_FILE_LIST;
}
private File getDatabasesDir() {
synchronized (mSync) {
if (mDatabasesDir == null) {
mDatabasesDir = new File(getDataDirFile(), "databases");
}
if (mDatabasesDir.getPath().equals("databases")) {
mDatabasesDir = new File("/data/system");
}
return mDatabasesDir;
}
}
private File validateFilePath(String name, boolean createDirectory) {
File dir;
File f;
if (name.charAt(0) == File.separatorChar) {
String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
dir = new File(dirPath);
name = name.substring(name.lastIndexOf(File.separatorChar));
f = new File(dir, name);
} else {
dir = getDatabasesDir();
f = makeFilename(dir, name);
}
if (createDirectory && !dir.isDirectory() && dir.mkdir()) {
// FileUtils.setPermissions(dir.getPath(),
// FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
// -1, -1);
}
return f;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
DatabaseErrorHandler errorHandler) {
if (!ENBABLE_FILE) {
return super.openOrCreateDatabase(name, mode, factory, errorHandler);
}
File f = validateFilePath(name, true);
int flags = SQLiteDatabase.CREATE_IF_NECESSARY;
if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) {
flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;
}
SQLiteDatabase db = SQLiteDatabaseCompat.openDatabase(f.getPath(), factory, flags, errorHandler);
setFilePermissionsFromMode(f.getPath(), mode, 0);
return db;
}
static void setFilePermissionsFromMode(String name, int mode,
int extraPermissions) {
Log.w(TAG, "not impl setFilePermissionsFromMode");
// int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR
// |FileUtils.S_IRGRP|FileUtils.S_IWGRP
// |extraPermissions;
// if ((mode&MODE_WORLD_READABLE) != 0) {
// perms |= FileUtils.S_IROTH;
// }
// if ((mode&MODE_WORLD_WRITEABLE) != 0) {
// perms |= FileUtils.S_IWOTH;
// }
// if (DEBUG) {
// Log.i(TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode)
// + ", perms=0x" + Integer.toHexString(perms));
// }
// FileUtils.setPermissions(name, perms, -1, -1);
}
@Override
public boolean deleteDatabase(String name) {
if (!ENBABLE_FILE) {
return super.deleteDatabase(name);
}
try {
File f = validateFilePath(name, false);
return SQLiteDatabaseCompat.deleteDatabase(f);
} catch (Exception e) {
}
return false;
}
@Override
public File getDatabasePath(String name) {
if (!ENBABLE_FILE) {
return super.getDatabasePath(name);
}
return validateFilePath(name, false);
}
static class SharedPrefImplCompat {
public static void startReloadIfChangedUnexpectedly(Object sp) {
try {
Method m = Class.forName("android.app.SharedPreferencesImpl").getDeclaredMethod("startReloadIfChangedUnexpectedly", new Class[]{});
m.setAccessible(true);
m.invoke(sp, new Object[]{});
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Object newObject(File prefsFile, int mode) {
try {
Constructor<?> c = Class.forName("android.app.SharedPreferencesImpl").getDeclaredConstructor(new Class[]{File.class, int.class});
c.setAccessible(true);
return c.newInstance(new Object[]{prefsFile, mode});
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
static class SQLiteDatabaseCompat {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static SQLiteDatabase openDatabase(String path,
CursorFactory factory, int flags,
DatabaseErrorHandler errorHandler) {
try {
Method m = Class.forName("android.database.sqlite.SQLiteDatabase")
.getDeclaredMethod("openDatabase",
new Class[]{String.class, CursorFactory.class, int.class, DatabaseErrorHandler.class});
m.setAccessible(true);
return (SQLiteDatabase) m.invoke(null, new Object[]{path, factory, flags, errorHandler});
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
//copied form SQLiteDatabase
// public static SQLiteDatabase openDatabaseX(String path, CursorFactory factory, int flags,
// DatabaseErrorHandler errorHandler) {
// SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
// db.open();
// return db;
// }
public static boolean deleteDatabase(File file) {
if (file == null) {
throw new IllegalArgumentException("file must not be null");
}
boolean deleted = false;
deleted |= file.delete();
deleted |= new File(file.getPath() + "-journal").delete();
deleted |= new File(file.getPath() + "-shm").delete();
deleted |= new File(file.getPath() + "-wal").delete();
File dir = file.getParentFile();
if (dir != null) {
final String prefix = file.getName() + "-mj";
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File candidate) {
return candidate.getName().startsWith(prefix);
}
});
if (files != null) {
for (File masterJournal : files) {
deleted |= masterJournal.delete();
}
}
}
return deleted;
}
}
}