Copy / share configurations between paid / free versions of Android app?

My Android app comes with both free and paid versions. I created a library project and two additional applications, one "Free" and one "Paid" version (of course, with the same key). Please note that these application projects are pretty empty, no settings, etc. Therefore, the library contains 99% of the code.

My application creates a SQLite and SharedPreferences with user data. Can I copy these files between the free and paid versions? (Preferences are more important than the database.)

eg.

  • User launches free version. The database and configuration file are created.
  • The user installs the paid version and launches it.
  • The paid version checks for free versions and copies it. This is what I want!
+7
source share
3 answers
  • Implement ContentProvider to publish saved data in your free version.
  • Make sure the provider is exported (android: exported = "true")
  • Declare permission in the client application. The security level must be a "signature".
  • Require permission declared in (3) as readPermission for the provider.
  • In your paid application, add the usage permission for the permission declared in your free application.
  • Check the availability of the provider and upload the data to your paid application.

This, of course, only works if you sign up free and paid applications with the same certificate (which the most reasonable people do).

+8
source

If you do not want to solve the problem with ContentProvider , or if it is possible that both applications can be installed and used, there is another solution.

Code and Usage

Assume that the data in question belongs to the class:

 class DataToBeShared() { // Data etc in here } 

Then add the class to both applications as follows:

 public class StoredInfoManager { public static String codeAppType = "apptype"; public static String codeTimestamp = "timestamp"; public static String codeData = "data"; public static String codeResponseActionString = "arstring"; public static String responseActionString = "com.me.my.app.DATA_RESPONSE"; private static int APP_UNKNOWN = 0; private static int APP_FREE = 1; private static int APP_PAID = 2; private static String freeSharedPrefName = "com.me.my.app.free.data"; private static String paidSharedPrefName = "com.me.my.app.paid.data"; // Use only one pair of the next lines depending on which app this is: private static String prefName = freeSharedPrefName; private static int appType = APP_FREE; //private static String prefName = paidSharedPrefName; //private static int appType = APP_PAID; private static String codeActionResponseString = "response"; // Provide access points for the apps to store the data public static void storeDataToPhone(Context context, DataToBeShared data) { SharedPreferences settings = context.getSharedPreferences(prefName, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); // Put the data in the shared preferences using standard commends. // See the android developer page for SharedPreferences.Editor for details. // Code for that here // And store it editor.commit(); } 

This is still a fairly standard system for storing shared preferences. Now the fun begins. First, make sure that there is a private method for retrieving the data stored above and a private method for broadcasting it.

  private static DataToBeshared getData(Context context) { SharedPreferences settings = context.getSharedPreferences(prefName, Context.MODE_PRIVATE); DataToBeShared result = new DataToBeShared(); // Your code here to fill out result from Shared preferences. // See the developer page for SharedPreferences for details. // And return the result. return result; } private static void broadcastData(Context context, DataToBeShared data, String intentActionName) { Bundle bundle = new Bundle(); bundle.putInt(codeAppType, appType); bundle.putParcelable(codeData, data); Intent intent = new Intext(intentActionString); intent.putEXtras(bundle); context.sendBroadcast(intent); } 

Create a BroadcastReceiver class to catch data responses from another application for our data:

 static class CatchData extends BroadcastReceiver { DataToBeShared data = null; Long timestamp = 0L; int versionListeningFor = Version.VERSION_UNKNOWN; Timeout timeout = null; // We will need a timeout in case the other app isn't actually there. class Timeout extends CountDownTimer { Context _context; public Timeout(Context context, long millisInFuture, long countDownInterval) { super(millisInFuture, countDownInterval); _context = context; } @Override public void onFinish() { broadcastAndCloseThisBRdown(_context); } @Override public void onTick(long millisUntilFinished) {} } // Constructor for the catching class // Set the timeout as you see fit, but make sure that // the tick length is longer than the timeout. CatchDPupdate(Context context, DataToBeShared dptsKnown, Long timeKnown, int otherVersion) { data = dptsKnown; timestamp = timeKnown; versionListeningFor = otherVersion; timeout = new Timeout(context, 5000, 1000000); timeout.start(); } @Override public void onReceive(Context context, Intent intent) { Bundle extras = intent.getExtras(); if (extras == null) return; // Check it the data we want int sendingVersion = extras.getInt(codeAppType, APP_UNKNOWN); if (sendingVersion != versionListeningFor) return; // This receiver has served its purpose, so unregister it. context.unregisterReceiver(this); // We've got the data we want, so drop the timeout. if (timeout != null) { timeout.cancel(); timeout = null; } Long tsInc = extras.getLong(codeTimestamp, 0L); DataToBeShared dataInc = extras.getParcelable(codeData); // Now, you need to decide which set of data is better. // You may wish to use a timestamp system incorporated in DataToBeStored. if (/* Incoming data best*/) { data = dpInc; // Make it ours for the future storeDataToPhone(context, data); } // Send the data out broadcastAndCloseThisBRdown(context); } private void broadcastAndCloseThisBRdown(Context context) { broadcastData(context, data, responseActionString); } } 

We will now provide a static access feature for using applications. Note that it does not return anything that was done by the response catcher above.

  public static void geDataFromPhone(Context context) { DataToBeStored myData = getData(context); // See security discussion point 2 for this next line String internalResponseActionString = "com.me.my.app.blah.hohum." + UUID.randomUUID(); // Instantiate a receiver to catch the response from the other app int otherAppType = (appType == APP_PAID ? APP_FREE : APP_PAID); CatchData catchData = new CatchData(context, mydata, otherAppType); context.registerReceiver(catchData, new IntentFilter(internalResponseActionString)); // Send out a request for the data from the other app. Bundle bundle = new Bundle(); bundle.putInt(codeAppType, otherAppType); bundle.putString(codeResponseActionString, internalResponseActionString); bundle.putString(CatchDataRequest.code_password, CatchDataRequest.getPassword()); Intent intent = new Intent(responseActionString); context.sendBroadcast(intent); } 

This is the core. We need another class, as well as a manifest twist. Class (to catch requests from another data application:

 public class CatchDataRequest extends BroadcastReceiver { // See security discussion point 1 below public static String code_password = "com.newtsoft.android.groupmessenger.dir.p"; public static String getPassword() { return calcPassword(); } private static String calcPassword() { return "password"; } private static boolean verifyPassword(String p) { if (p == null) return false; if (calcPassword().equals(p)) return true; return false; } @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); if (bundle == null) return; String passwordSent = bundle.getString(code_password); if (!verifyPassword(passwordSent)) return; int versionRequested = bundle.getInt(StoredInfoManager.codeAppType); String actionStringToRespondWith = bundle.getString(StoredInfoManager.codeResponseActionString); // Only respond if we can offer what asked for if (versionRequested != StoredInfoManager.appType) return; // Get the data and respond DataToBrStored data = StoredInfoManager.getData(context); StoredInfoManager.broadcastData(context, data, actionStringToRespondWith); } } 

In the manifest, be sure to declare this class as the receiver with the action name matching StoredInfoManager.responseActionString

 <receiver android:name="com.me.my.app.CatchDataRequest" android:enabled="true"> <intent-filter> <action android:name="com.me.my.app.DATA_RESPONSE"/> </intent-filter> </receiver> 

Using this is relatively simple. The class in which you use the data must extend BroadcastReceiver:

 public class MyActivity extends Activity { // Lots of your activity code ... // You'll need a class to receive the data: MyReceiver receiver= new MyReceiver(); class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Bundle extras = intent.getExtras(); if (extras == null) return; // Do stuff with the data } } // But be sure to add the receiver lines to the following methods: @Override public void onPause() { super.onPause(); this.unregisterReceiver(receiver); } @Override public void onResume() { super.onResume(); this.registerReceiver(receiver, new IntentFilter(StoredInfoManager.receiver_action_string)); } } // To store the data StoredInfoManager.storeDataToPhone(contextOfApp, data); // To retrieve the data is a two step process. Ask for the data: StoredInfoManager.getData(contextOfApp); // It will arrive in receiver, above. } 

Security

The weakness of this method is that anyone can register the receiver to catch the connection between the two applications. The above code goes around this:

  • Make the broadcast request programmed by using a password. This answer is not suitable for discussing how you can make this password secure, but it is important to understand that you cannot store data when creating a password to verify it later - this is another application that will be checked.

  • Eliminate the complexity of the response using a unique action code each time.

None of them is proof of a fool. If you just bypass your favorite application colors, you probably don't need any security measures. If you transmit more confidential information, you need both, and you need to think about making the password properly protected.

Other improvements

+1
source

I gathered information from several stackoverflow answers to provide a way to copy all SharedPreference data from one application to another. In my particular case, I use products for a free and a pro application, and I want to copy from free to pro.

ATTENTION: This only works if you have not released any of the versions in the play store. If you add (or delete) sharedUserId to your application after it is in the playback store, your users will not be able to update without deleting, I learned this difficult way. Thanks Google ..

Add sharedUserId to your manifest in both applications. Please note that this will only work if both applications are signed with the same certificate.

 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="my.package.name.free" android:sharedUserId="my.package.name"> 

Then call this method when you first run the pro application.

 private void getSettingsFromFreeApp() { // This is a build config constant to check which build flavour this is if (BuildConfig.IS_PRO) { try { Context otherAppContext = this.createPackageContext("my.package.name.free", Context.MODE_PRIVATE); SharedPreferences otherAppPrefs = PreferenceManager.getDefaultSharedPreferences(otherAppContext); Map<String, ?> keys = otherAppPrefs.getAll(); SharedPreferences.Editor editor = prefs.edit(); for(Map.Entry<String, ?> entry : keys.entrySet()){ Object value = getWildCardType(entry.getValue()); Log.d("map values", entry.getKey() + ": " + entry.getValue()); if (entry.getValue() instanceof Boolean) { editor.putBoolean(entry.getKey(), (boolean) value); editor.apply(); } else if (value instanceof Long) { editor.putLong(entry.getKey(), (long) value); editor.apply(); } else if (value instanceof Float) { editor.putFloat(entry.getKey(), (float) value); editor.apply(); } else if (value instanceof Integer) { editor.putInt(entry.getKey(), (int) value); editor.apply(); } else if (value instanceof String) { editor.putString(entry.getKey(), String.valueOf(value)); editor.apply(); } } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } } private Object getWildCardType(Object value) { return value; } 

In addition, according to this answer, you want to call getSettingsFromFreeApp() before any other call to get the settings in your application.

0
source

All Articles