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.
Related
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 a some data which am storing in preferences.If any updates found in PlayStore.I need to listen to the update action and have to clear the caches of myapp.
To know if your app has been updated :
Store the current version code in shared preferences, and then use the below function in the main activity.
public static AppStart checkAppStart(Context context, SharedPreferences sharedPreferences) {
PackageInfo pInfo;
AppStart appStart = AppStart.NORMAL;
try {
pInfo = context.getPackageManager().getPackageInfo(
context.getPackageName(), PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
int lastVersionCode = sharedPreferences.getInt(
Constants.LAST_APP_VERSION, -1);
int currentVersionCode = pInfo.versionCode;
appStart = checkAppStart(currentVersionCode, lastVersionCode);
// Update version in preferences
sharedPreferences.edit()
.putInt(Constants.LAST_APP_VERSION, currentVersionCode).commit(); // must use commit here or app may not update prefs in time and app will loop into walkthrough
} catch (PackageManager.NameNotFoundException e) {
Log.w(LOG_TAG,
"Unable to determine current app version from package manager. Defensively assuming normal app start.");
}
return appStart;
}
private static AppStart checkAppStart(int currentVersionCode, int lastVersionCode) {
if (lastVersionCode == -1) {
return AppStart.FIRST_TIME;
} else if (lastVersionCode < currentVersionCode) {
return AppStart.FIRST_TIME_VERSION;
} else if (lastVersionCode > currentVersionCode) {
Log.w(LOG_TAG, "Current version code (" + currentVersionCode
+ ") is less then the one recognized on last startup ("
+ lastVersionCode
+ "). Defensively assuming normal app start.");
return AppStart.NORMAL;
} else {
return AppStart.NORMAL;
}
}
AppStart is just an enum here:
public enum AppStart {
FIRST_TIME,
FIRST_TIME_VERSION,
NORMAL
}
After that, to clear the cache :
https://stackoverflow.com/a/23908638/1594776
I have an app on the Play Store. I want to put a requirement that if users want to use a certain part of the app, they have to invite a friend before being able to do so. But I only want to impose this restriction to new installs of the app (to be fair to users that have installed the app before the restriction).
Sorry for the long intro, my question is how can I find out if the current device has updated the app or is a new install?
public static boolean isFirstInstall(Context context) {
try {
long firstInstallTime = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).firstInstallTime;
long lastUpdateTime = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).lastUpdateTime;
return firstInstallTime == lastUpdateTime;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return true;
}
}
public static boolean isInstallFromUpdate(Context context) {
try {
long firstInstallTime = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).firstInstallTime;
long lastUpdateTime = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).lastUpdateTime;
return firstInstallTime != lastUpdateTime;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return false;
}
}
The only solution I can see that doesn't involve an entity outside of the device would be to get the PackageInfo for your app and check the values of
versionCode
firstInstallTime
lastUpdateTime
On first install, firstInstallTime and lastUpdateTime will have the same value (at least on my device they were the same); after an update, the values will be different because lastUpdateTime will change. Additionally, you know approximately what date and time you create the version that introduces this new behavior, and you also know which version code it will have.
I would extend Application and implement this checking in onCreate(), and store the result in SharedPreferences:
public class MyApplication extends Application {
// take the date and convert it to a timestamp. this is just an example.
private static final long MIN_FIRST_INSTALL_TIME = 1413267061000L;
// shared preferences key
private static final String PREF_SHARE_REQUIRED = "pref_share_required";
#Override
public void onCreate() {
super.onCreate();
checkAndSaveInstallInfo();
}
private void checkAndSaveInstallInfo() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs.contains(PREF_SHARE_REQUIRED)) {
// already have this info, so do nothing
return;
}
PackageInfo info = null;
try {
info = getPackageManager().getPackageInfo(getPackageName(), 0);
} catch (NameNotFoundException e) {
// bad times
Log.e("MyApplication", "couldn't get package info!");
}
if (packageInfo == null) {
// can't do anything
return;
}
boolean shareRequired = true;
if (MIN_FIRST_INSTALL_TIME > info.firstInstallTime
&& info.firstInstallTime != info.lastUpdateTime) {
/*
* install occurred before a version with this behavior was released
* and there was an update, so assume it's a legacy user
*/
shareRequired = false;
}
prefs.edit().putBoolean(PREF_SHARE_REQUIRED, shareRequired).apply();
}
}
This is not foolproof, there are ways to circumvent this if the user really wants to, but I think this is about as good as it gets. If you want to track these things better and avoid tampering by the user, you should start storing user information on a server (assuming you have any sort of backend).
Check if the old version of your app saves some data on disk or preferences. This data must be safe, i.e. it cannot be deleted by the user (I'm not sure it's possible).
When the new version is freshly installed, this data won't exist. If the new version is an upgrade from the old version, this data will exist.
Worst case scenario, an old user will be flagged as a new one and will have a restricted usage.
A concise Kotlin solution, based off the still excellent wudizhuo answer:
val isFreshInstall = with(packageManager.getPackageInfo(packageName, 0)) {
firstInstallTime == lastUpdateTime
}
This can be called directly from within an Activity as an Activity is a context (so can access packageManager etc.)
If you wanted to use this in multiple places/contexts, it could very easily be turned into an extension property:
val Context.isFreshInstall get() = with(packageManager.getPackageInfo(packageName, 0)) {
firstInstallTime == lastUpdateTime
}
This way, you can simply write if (isFreshInstall) in any Activity, or if (requireContext().isFreshInstall) inside any Fragment.
My solution is use SharedPreference
private int getFirstTimeRun() {
SharedPreferences sp = getSharedPreferences("MYAPP", 0);
int result, currentVersionCode = BuildConfig.VERSION_CODE;
int lastVersionCode = sp.getInt("FIRSTTIMERUN", -1);
if (lastVersionCode == -1) result = 0; else
result = (lastVersionCode == currentVersionCode) ? 1 : 2;
sp.edit().putInt("FIRSTTIMERUN", currentVersionCode).apply();
return result;
}
return 3 posibles values:
0: The APP is First Install
1: The APP run once time
2: The APP is Updated
Update
(thanks for the comments below my answer for prodding for a more specific/complete response).
Because you can't really retroactively change the code for previous versions of your app, I think the easiest is to allow for all current installs to be grandfathered in.
So to keep track of that, one way would be to find a piece of information that points to a specific version of your app. Be that a timestamped file, or a SharedPreferences, or even the versionCode (as suggested by #DaudArfin in his answer) from the last version of the app you want to allow users to not have this restriction. Then you need to change this. That change then becomes your reference point for all the previous installs. For those users mark their "has_shared" flag to true. They become grandfathered in. Then, going forward, you can set the "has_shared" default to true
(Original, partial answer below)
Use a SharedPrefence (or similar)
Use something like SharedPreferences.
This way you can put a simple value like has_shared = true and SharedPreferences will persist through app updates.
Something like this when they have signed someone up / shared your app
SharedPreferences prefs = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("has_shared", true)
editor.commit();
Then you can only bug people when the pref returns true
SharedPreferences prefs = getActivity().getPreferences(Context.MODE_PRIVATE);
boolean defaultValue = false;
boolean hasShared= prefs.gettBoolean("has_shared", defaultValue);
if (!hasShared) {
askUserToShare();
}
Docs for SharedPreference:
http://developer.android.com/training/basics/data-storage/shared-preferences.html
You can get the version code and version name using below code snippet
String versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
int versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
Now you can check for the latest version and restrict as per your requirement.
We can use broadcast receiver to listen app update.
Receiver
class AppUpgradeReceiver : BroadcastReceiver() {
#SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null) {
return
}
Toast.makeText(context, "Updated to version #${BuildConfig.VERSION_CODE}!", Toast.LENGTH_LONG).show()
}
}
Manifest
<receiver android:name=".AppUpgradeReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
It doesn't work while debug. So you have to install to manually.
Increase the versionCode in your app-level build.gradle (so it counts as an update).
Click Build -> Build Bundle(s) / APK(s) -> Build APK(s), and select a debug APK.
Run following command in the terminal of Android Studio:
adb install -r C:\Repositories\updatelistener\app\build\outputs\apk\debug\app-debug.apk
If you want to perform any operation only once per update then follow below code snippet
private void performOperationIfInstallFromUpdate(){
try {
SharedPreferences prefs = getActivity().getPreferences(Context.MODE_PRIVATE);
String versionName = prefs.getString(versionName, "1.0");
String currVersionName = getApplicationContext().getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
if(!versionName.equals(currVersionName)){
//Perform Operation which want execute only once per update
//Modify pref
SharedPreferences.Editor editor = prefs.edit();
editor.putString(versionName, currVersionName);
editor.commit();
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return BASE_VERSION;
}
}
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
I have a settings application from which i have to retrieve other applications preferences, but i don't have the details of keys in them, how can i retrieve all the available keys and values in that preference?
Thanks,
Swathi
Okay! using this code in Application 1 ( with package name is "com.sharedpref1" ) to store data with Shared Preferences.
SharedPreferences prefs = getSharedPreferences("demopref",
Context.MODE_WORLD_READABLE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("demostring", strShareValue);
editor.commit();
And using this code in Application 2 to get data from Shared Preferences in Application 1. We can get it because we use MODE_WORLD_READABLE in application 1:
try {
con = createPackageContext("com.sharedpref1", 0);
SharedPreferences pref = con.getSharedPreferences(
"demopref", Context.MODE_PRIVATE);
String data = pref.getString("demostring", "No Value");
displaySharedValue.setText(data);
} catch (NameNotFoundException e) {
Log.e("Not data shared", e.toString());
}
More information please visit this URL:
http://androiddhamu.blogspot.in/2012/03/share-data-across-application-in.html
Assuming the preference are WORLD_READABLE, this might work:
final ArrayList<HashMap<String,String>> LIST = new ArrayList<HashMap<String,String>>();
// where com.example is the owning app containing the preferences
Context myContext = createPackageContext("com.example", Context.MODE_WORLD_WRITEABLE);
SharedPreferences testPrefs = myContext.getSharedPreferences("test_prefs", Context.MODE_WORLD_READABLE);
Map<String, ?> items = testPrefs .getAll();
for(String s : items.keySet()) {
// do something like String value = items.get(s).toString());
}
Additionally you have to add same android:sharedUserId in the both app's manifest file.
Unfortunately the docs now don't even explain MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE, instead saying:
This constant was depreciated in API level 17.
Creating world-readable files is very dangerous, and likely to cause security holes in applications. It is strongly discouraged; instead, ....etc
Since the depreciation, implementing file sharing between apps with sharedpreferences may be too risky, although it was simple. I'm not too concerned with security holes from the MODE_WORLD_READABLE mode in game apps where I just want to be able to transfer characters from one app to another. It's too bad they depreciated both sharing modes.
It can work if we want read perference value from other app/pkg/process.
but there is something wrong in jkhouw1's answer:
Context myContext = createPackageContext("com.example",
Context.MODE_WORLD_WRITEABLE);
It should be :
Context myContext = createPackageContext("com.example",
Context.CONTEXT_IGNORE_SECURITY);
though , CONTEXT_IGNORE_SECURITY and MODE_WORLD_WRITEABLE with the same value of "int 2"
At all ,thanks for this question and answers.
It's simple to retrieve store shared preferences data of one application to another application.
Step 1: add the same android:sharedUserId="android.uid.shared" in both app's manifest files.
Step 2: Store Value application1
SharedPreferences preferences = context.getSharedPreferences("token_id", Context.MODE_WORLD_READABLE);
Editor editor = preferences.edit();
editor.putString("shared_token", encryptedValue);
Log.e("aaa *** shared_token : ", encryptedValue.toString());
editor.commit();
Step 3: Get Value From application2
Context con = null;
try {
con = createPackageContext("application2 package name", Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
try {
if (con != null) {
SharedPreferences pref = con.getSharedPreferences(
"token_id", Context.MODE_WORLD_READABLE);
String data = pref.getString("shared_token", "");
Log.d("msg", "Other App Data: " + data);
} else {
Log.d("msg", "Other App Data: Context null");
}
} catch (Exception e) {
e.printStackTrace();
}