Is it possible to save SharedPreferences files into custom dir? Let's say into /data/data/package.name/my_prefs/.
Or is it possible to retrieve the directory SharedPreferences are saved by default to?
P.S. Hardcoding the path /data/data/package.name/shared_prefs/ is not the solution.
Or is it possible to retrieve the directory SharedPreferences are
saved by default to?
Yes.
This is basically the dataDir /shared_prefs which you can get from the ApplicationInfo object (which in turn you can get from the PackageManager). (Also, it might be the same as the "getFilesDir" dir you can get easily from Context itself? Not sure, didn't check that.)
From the source, starting with Context.getSharedPreferences (ContextImpl source):
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
File prefsFile;
boolean needInitialLoad = false;
synchronized (sSharedPrefs) {
sp = sSharedPrefs.get(name);
if (sp != null && !sp.hasFileChangedUnexpectedly()) {
return sp;
}
prefsFile = getSharedPrefsFile(name);
...
public File getSharedPrefsFile(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
}
return mPreferencesDir;
}
}
private File getDataDirFile() {
if (mPackageInfo != null) {
return mPackageInfo.getDataDirFile();
}
throw new RuntimeException("Not supported in system context");
}
And "mPackageInfo" is an instance of LoadedApk:
public File getDataDirFile() {
return mDataDirFile;
}
mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
mDataDir = aInfo.dataDir;
And that brings us back to ApplicationInfo.
I'd say if you don't want to rely on the convention /data/data/<package_name>/shared_prefs then it should be safe to get the "dataDir" and rely on "shared_prefs" from there?
Or is it possible to retrieve the directory SharedPreferences are
saved by default to?
See this answer to know how get path in safe way: https://stackoverflow.com/a/33849650/1504248
Related
I have two apps ,lets say A and B. App A has a shared pref which is created as world readable so that app B can access it.
When i try to access the App A's shared pref value from app B for the very first time, it gives correct result. But the problem come when i change the shared pref value of app A and then open app B to check the updated shared pref it gives the same old value. And surprisingly when i force close the app B from settings--> apps and reopen the app B it gives the correct updated value of app A's shared pref.
What is the problem with accessing shared pref a application when it is WORLD_READABLE.
Below is the source code of app B when i am accessing the Shared pref of app A.
private boolean isDAEnabled() throws SecurityException {
Context context = null;
try {
context = createPackageContext(APP_A_PACKAGE_NAME, Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (context == null) {
throw new SecurityException("can not read shared pref of old DAE");
}
SharedPreferences oldDaPrefs = context.getSharedPreferences
(A_SHAREDPREF_FILE_NAME, Context.MODE_WORLD_READABLE);
int what = oldDaPrefs.getInt(A_PREF_ENGINE_STATE, 4);
Log.d(TAG, "What is value "+ what);
return what == ENABLE_ENGINE; // ENABLE_ENGINE IS == 0
}
And Below is the code where i am changing the shared prefs of App A
private void setEnginePreference(boolean engineStatus) {
mPreferenceEditor = mPreference.edit();
if(engineStatus){
mPreferenceEditor.putInt(Constants.PREF_ENGINE_STATE, ENABLE_ENGINE);
} else {
mPreferenceEditor.putInt(Constants.PREF_ENGINE_STATE, DISABLE_ENGINE);
}
mPreferenceEditor.commit();
}
Change this :
private void setEnginePreference(boolean engineStatus) {
mPreferenceEditor = mPreference.edit();
if(engineStatus){
mPreferenceEditor.putInt(Constants.PREF_ENGINE_STATE, ENABLE_ENGINE);
} else {
mPreferenceEditor.putInt(Constants.PREF_ENGINE_STATE, DISABLE_ENGINE);
}
mPreferenceEditor.commit();
}
To :
private void setEnginePreference(boolean engineStatus) {
mPreferenceEditor = mPreference.edit();
if(engineStatus){
mPreferenceEditor.putInt(Constants.PREF_ENGINE_STATE, ENABLE_ENGINE);
} else {
mPreferenceEditor.putInt(Constants.PREF_ENGINE_STATE, DISABLE_ENGINE);
}
mPreferenceEditor.apply();
}
Answering my own question as i have figured out the solution.
I found in the blog that i can use MODE_MULTI_PROCESS while accessing the shared prefs of the different app(i.e. app A in my case). However, as of API Level 23 (OS version 6.0), Context.MODE_MULTI_PROCESS is deprecated. But my requirement was only to support API level 23 so it was good for me.
Here is the change i had to make
private boolean isDAEnabled() throws SecurityException {
Context context = null;
try {
context = createPackageContext(APP_A_PACKAGE_NAME, Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (context == null) {
throw new SecurityException("can not read shared pref of old DAE");
}
SharedPreferences oldDaPrefs = context.getSharedPreferences
(A_SHAREDPREF_FILE_NAME, Context.MODE_MULTI_PROCESS);
int what = oldDaPrefs.getInt(A_PREF_ENGINE_STATE, 4);
Log.d(TAG, "What is value "+ what);
return what == ENABLE_ENGINE; // ENABLE_ENGINE IS == 0}
I hope that it will help others.
Problem
I'm saving a byte[] in my shared preferences. I am able to close the app and reopen it with the value persisting in the Shared Preferences. When running the app and closing it via the 'Task Manager' or 'Force Close', the Shared Preference value for the byte[] is cleared. I don't understand this because other values persist fine.
This lead me to believe that this was due to some gson or Shared Preference issue with the byte[] so I converted it to a String and I still have the issue.
Edit:
I save the data during normal activity usage... after onCreate(), for example. It's not during onPuse() or onDestroy() I forgot to mention this. It would make sense if I did call it here and one or both of those weren't being called on the 'Force Close' scenario.
Shared Preference Code
Slightly modified to remove app specific implementation and data
private static final String SHARED_PREFERENCES_FILE_NAME = "SharedPreferenceName";
public static void setSharedPreferenceObjectBase64Encoded(Context context, String key, Object object) throws Exception {
// Need an editor to update shared preference values
SharedPreferences.Editor editor = context.getSharedPreferences(SHARED_PREFERENCES_FILE_NAME, Context.MODE_PRIVATE).edit();
Gson gson = new GsonBuilder().serializeNulls().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").create();
String encodedKey = Base64.encodeToString(key.getBytes(), 0, key.getBytes().length, Base64.DEFAULT);
String stringObject = gson.toJson(object);
String encodedObject = Base64.encodeToString(stringObject.getBytes(), 0, stringObject.getBytes().length, Base64.DEFAULT);
editor.putString(encodedKey, encodedObject);
editor.apply();
}
public static Object getSharedPreferenceObjectBase64Encoded(Context context, String key, Class<? extends Serializable> objectClass) throws Exception {
// Need an editor to update shared preference values
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_FILE_NAME, Context.MODE_PRIVATE);
Gson gson = new GsonBuilder().serializeNulls().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").create();
String encodedKey = Base64.encodeToString(key.getBytes(), 0, key.getBytes().length, Base64.DEFAULT);
String encodedObject = prefs.getString(encodedKey, null);
if (encodedObject == null) {
throw new NullPointerException("encodedObject is null : No shared preference exists for key.");
}
String decodedObject = new String(Base64.decode(encodedObject, Base64.DEFAULT));
if(decodedObject == null){
throw new NullPointerException("decodedObject is null : Json decoding error.");
}
Object resultObject = gson.fromJson(decodedObject, objectClass);
if (resultObject == null) {
throw new NullPointerException("resultObject is null : Json decoding error.");
}
return resultObject;
}
`byte[]` Code
public static final String VALUE_KEY= "value.key";
public static void saveTheValue(Context context, byte[] encryptedPin) {
try {
USharedPreferenceManager.setSharedPreferenceObjectBase64Encoded(context, VALUE_KEY, encryptedPin);
} catch (Exception e) {
}
}
public static byte[] getTheValue(Context context) {
try {
return (byte[]) USharedPreferenceManager.getSharedPreferenceObjectBase64Encoded(context, VALUE_KEY, byte[].class);
} catch (Exception e) {
return null;
}
}
Any input would be greatly appreciated..
Sadly, I haven't been able to make any progress here. Any thoughts?
Update:
As per Super-califragilistic recommendation, I iterated through the key/value pairs in the SharedPreferences immediately before retrieving the value. I was Base64 encoding my key and value values; in order to read the key to ensure the value was in the SharedPreferences I had to use the keys in plain text. This solved the issue for me as the byte[] value was now being retrieved properly.
This seems strange to me but I can use it as a solution. I would still like to Base64 encode the keys, but it's not incredibly important.
Current Solution:
Removed the Base64 encoding of the SharedPreference Key for storage and retrieval and the value is now persisting in all cases.
This line of code String encodedObject = prefs.getString(encodedKey, null); means if the key does not exist it should return null, hence your key that you are checking does not exist.
To validate if your key/value exist use this code
for(Entry<String, ?> en : sharedPref.getAll().entrySet()){
en.getKey();//key
en.getValue();//value
}
you could stop that from happening override onPause() in the Activity or Fragment and call saveTheValue(Context context, byte[] encryptedPin) if you detect you need to save data or have already tried saving data eg.
private boolean forceSaveInOnPause= false;//global variable
//in your saving method
....//after you save
forceSaveInOnPause = true;
//in your onPause of Activity
if(forceSaveInOnPause){
//re-save
forceSaveInOnPause = false;
but since you already have a solution scratch all that :)
Try once with editor.commit() instead of apply(), see if that works
I think using Base64.NO_PADDING instead of Base64.DEFAULT both while reading and writing may solve the problem.
I have been using "Environment.getExternalStorage()" to store and manage files. And there is no warning message from logcat with that method and works greatly fine.
But, My project needs to use method "Context.getExternalFilesDir(String type)" and there is a warning message
ContextImpl:Failed to ensure directory: /storage/external_SD/Android/data/(package name)/files
Fortunately that File object works fine(reading or write or making folder works, too).
But I want to know how to resolve that warning message. Do I miss something?
You should know how the warning message comes up.
The getExternalFilesDir(String type) will call getExternalFilesDirs(String type) (notice the 's' at the final of the second method name).
The getExternalFilesDirs(String type) will find all dirs of the type, and calls ensureDirsExistOrFilter() at the end to ensure the directories exist.
If the dir can't be reached, it will print a warning!
Log.w(TAG, "Failed to ensure directory: " + dir);
dir = null;
So, if your device has two sdcard paths, it will produce two dirs. If one is not available, the warning will come up.
The conclusion is the warning does not need to be fixed.
If you have code that is iterating files, calling this API many times, this warning can cause log pollution. To solve this (since the warning is actually benign) you can create a wrapper class that stores the result of calling getExternalFilesDir / getExternalCacheDir and subsequently returns the stored value instead of calling the API. In this way, at least you will only ever see this message once.
I follow the getExternalFilesDir() source
/**
* Ensure that given directories exist, trying to create them if missing. If
* unable to create, they are filtered by replacing with {#code null}.
*/
private File[] ensureExternalDirsExistOrFilter(File[] dirs) {
File[] result = new File[dirs.length];
for (int i = 0; i < dirs.length; i++) {
File dir = dirs[i];
if (!dir.exists()) {
if (!dir.mkdirs()) {
// recheck existence in case of cross-process race
if (!dir.exists()) {
// Failing to mkdir() may be okay, since we might not have
// enough permissions; ask vold to create on our behalf.
final IMountService mount = IMountService.Stub.asInterface(
ServiceManager.getService("mount"));
try {
final int res = mount.mkdirs(getPackageName(), dir.getAbsolutePath());
if (res != 0) {
Log.w(TAG, "Failed to ensure " + dir + ": " + res);
dir = null;
}
} catch (Exception e) {
Log.w(TAG, "Failed to ensure " + dir + ": " + e);
dir = null;
}
}
}
}
result[i] = dir;
}
return result;
}
immediate use Environment.getExternalStorageDirectory() get ExternalDirs
public final class StorageUtil {
public static final String DIR_ANDROID = "Android";
private static final String DIR_DATA = "data";
private static final String DIR_FILES = "files";
private static final String DIR_CACHE = "cache";
#Nullable
public static synchronized File getExternalStorageAppFilesFile(Context context, String fileName) {
if (context == null) return null;
if (fileName == null) return null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File dirs = buildExternalStorageAppFilesDirs(Environment.getExternalStorageDirectory().getAbsolutePath(), context.getPackageName());
return new File(dirs, fileName);
}
return null;
}
public synchronized static File buildExternalStorageAppFilesDirs(String externalStoragePath, String packageName) {
return buildPath(externalStoragePath, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
}
public synchronized static File buildPath(String base, String... segments) {
File cur = new File(base);
for (String segment : segments) {
cur = new File(cur, segment);
}
return cur;
}
}
I'm looking at making a change in an app I'm working on (it's based off of this: http://goo.gl/rDBXVl) from loading a cloud based resource to a local based resource. I'm not particularly sure how I would go about doing this. I want to go from pulling a JSON file off the internet to pulling the JSON from my Assets folder.
I located the area in the app where it pulls the URL and loads the JSON but am unsure of what changes to make at this point.
public void loadData (Bundle savedInstanceState) {
// Check Network State
if (!NetworkUtil.getNetworkState(this)) {
final RetryFragment fragment = RetryFragment.getFragmentWithMessage("No connection");
this.addFragment(fragment, RetryFragment.TAG, true);
return;
}
if (savedInstanceState == null || savedInstanceState.get(KEY_LIST_DATA) == null) {
final String url = super.getResources().getString(R.string.config_wallpaper_manifest_url);
if (url != null && URLUtil.isValidUrl(url)) {
// Add Loading Fragment
final LoadingFragment fragment = new LoadingFragment();
this.addFragment(fragment, LoadingFragment.TAG, true);
// Load Data
final RestClientHandler handler = new RestClientHandler(this);
RestClient.get(this, url, handler);
}
} else {
Log.i(TAG, "Restored Instance");
this.mData = (ArrayList<NodeCategory>) savedInstanceState.get(KEY_LIST_DATA);
this.mPosition = savedInstanceState.getInt(KEY_LIST_POSITION);
if (this.mPosition != -1) {
mIgnoreSelection = true;
}
this.configureActionBar();
}
}
You have another option,
just save json in sharedpreferences. so easily read and write it.
Save sharedpreferences code bellow.
/**
* write SharedPreferences
* #param context
* #param name, name of preferences
* #param value, value of preferences
*/
public static void writePreferences(Context context,String name,String value)
{
SharedPreferences setting= context.getSharedPreferences("Give_your_filename", Context.MODE_PRIVATE);
SharedPreferences.Editor editor=setting.edit();
editor.putString(name, value);
editor.commit();
}
I want to store some basic data like players_name,levels_completed,fastest_speed,fastest_time and bring it up each time the player starts a silly little game I am making... which is the perffered method to do this?
Sharedprefs or internal storage?
I am at http://developer.android.com/guide/topics/data/data-storage.html
and have just gotten confused as to which to use as both look good and easy enough to do.
Advise?
Thanks!
This is pretty much taken from one of the Facebook sdks examples (it allows you to save the FB session so the user doesn't have to login each time)... I'll modify it a bit for you though
public class SessionStore {
private static final String PLAYER_NAME = "player_name";
private static final String LEVELS_COMPLETED = "levels_completed";
private static final String HIGH_SCORE = "high_score";
private static final String KEY = "player_session";
int highScore;
int levelsCompleted;
String pName;
public static boolean saveSession(Context context, String player_name, int levels_completed, int high_score) {
Editor editor =
context.getSharedPreferences(KEY + player_name, Context.MODE_PRIVATE).edit();
editor.putString(PLAYER_NAME,player_name);
editor.putInt(LEVELS_COMPLETED, levels_completed);
editor.putInt(HIGH_SCORE,high_score);
return editor.commit();
}
public static void restoreSession(Context context, String player_name) {
SharedPreferences savedSession =
context.getSharedPreferences(KEY + player_name, Context.MODE_PRIVATE);
highScore = savedSession.getInt(HIGH_SCORE,0);
levelsCompleted = savedSession.getInt(LEVELS_COMPLETED,0);
pName = savedSession.getString(PLAYER_NAME,"NO_NAME!");
}
public String getName()
{
return pName;
}
}
I think you get the basic idea...
some points: I use "KEY + player_name" in case different players play on the same phone (if it was static you would overwrite the data of one player with anothers data).
Also, when you do pName = savedSession.getString(PLAYER_NAME,"NO_NAME!"); if nothing exists in the shared preferences it defaults to the value "NO_NAME!" and likewise for the getInts (which in this case I have them default to 0)
in the program you would do SessionStore.saveSession("Alex",50000,50000); to save the session, etc. Hope this gives a good gist of how to use it... Also keep in mind I'm an android newb - this works great for me but I'm no expert :D
If it is game data that is static you could use shared preferences. If it is dynamic data like player high scores etc I would use sqlite database. I actually think that a database is a simpler option as creating read / write buffers can be a bit tricky on internal storage.
public void StoreTimeAppend(String MY_DATA, File file) {
try {
FileOutputStream fos = openFileOutput(String.valueOf(file), MODE_APPEND);
Writer out = new OutputStreamWriter(fos, "UTF8");
out.write(MY_DATA + '\n');
out.close();
fos.close();
} catch (IOException e) {
Tip.setText("ISSUE");
}
}