/*
* Copyright (C) 2012 mixi, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jp.mixi.compatibility.android.content;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Workaround helper for the issue of {@link SharedPreferences} with Galaxy S.
*/
public final class SharedPreferencesCompat {
public static final String TAG = SharedPreferencesCompat.class.getSimpleName();
private static final String SAMSUNG_PREF_DIR_BASE = "/dbdata/databases";
private static Boolean sIsNeedPreferenceWorkaround = null;
/**
* Do not instantiate this class.
*/
private SharedPreferencesCompat() {}
/**
* As a workaround for Galaxy S /dbdata/databases permission bug, this
* function will make an injection to the implementation of Context to
* change a directory used by SharedPreferences. Called from
* {@link YourApplication#onCreate()} when the application starts.
*
* @param context injection target context.
*/
public static void injectPrefencesDirIfNeeded(Context context) {
if (isNeedPreferenceWorkaround(context))
injectPreferencesDir(context);
}
/**
* Wrapper method of getting default shared preferences.
*
* @param context a context
* @return the default shared preferences.
*/
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
}
/**
* Wrapper method of getting shared preferences.
*
* @param context a context
* @param name a preference name.
* @param mode operating mode.
* @return a {@link SharedPreferences} for a specified name.
*/
public static SharedPreferences getSharedPreferences(Context context, String name, int mode) {
return context.getApplicationContext().getSharedPreferences(name, mode);
}
/**
* Checks whether we should inject to the implementation of Context for workaround or not.
*
* @param context a context that should be investigated.
* @return true if we've lost a permission to deal with shared preferences directory.
*/
public static boolean isNeedPreferenceWorkaround(Context context) {
if (sIsNeedPreferenceWorkaround == null) {
File baseDir = new File(SAMSUNG_PREF_DIR_BASE, context.getPackageName());
boolean result = baseDir.exists() && !baseDir.canWrite();
if (result)
Log.v(TAG, "need /dbdata workaround");
sIsNeedPreferenceWorkaround = result;
}
return sIsNeedPreferenceWorkaround.booleanValue();
}
/**
* Injects {@link SharedPreferences} directory to enable using proper {@link SharedPreferences}.
*
* @param context a context.
*/
private static void injectPreferencesDir(Context context) {
if (ContextWrapper.class.isInstance(context)) {
ContextWrapper wrapper = (ContextWrapper) context;
context = wrapper.getBaseContext();
}
// inject to overwrite inner ContextImpl#mPreferencesDir
try {
Class<?> clazz = Class.forName("android.app.ContextImpl");
Field field = clazz.getDeclaredField("mPreferencesDir");
field.setAccessible(true);
if (field.get(context) != null)
return;
// build shared_prefs path and try to overwrite
Method getDataDirFile = clazz.getDeclaredMethod("getDataDirFile");
getDataDirFile.setAccessible(true);
File dataDir = (File) getDataDirFile.invoke(context);
field.set(context, new File(dataDir, "shared_prefs"));
} catch (Exception ignored) {
Log.w(TAG, "failed to apply Samsung Galaxy broken /dbdata workaround.", ignored);
}
}
}