41

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?

Hasam
  • 622
  • 1
  • 6
  • 14
  • Does your app communicate with a backend? Do you have some way to identify users outside of the device itself? – Karakuri Oct 14 '14 at 04:57
  • 1
    Check if the old version of your app saves some data on disk or preferences, that is safe (i.e. that cannot be deleted by the user). 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. – Philippe A Oct 14 '14 at 05:15
  • I would insist to checkout preference data of existing version. – Mehul Joisar Oct 14 '14 at 05:15
  • That sounds pretty good, not a perfect solution though because there is still a chance that older versions have nothing in SharedPrefereces. If there isn't a better solution, I will have to use that though. – Hasam Oct 14 '14 at 05:19
  • 1
    @Hasam: AFAIK, your requirement is unique, and so is the solution. To identify existing users, you need to checkout their footprints in device only. and as you know, SharedPreference is one of the ways because it persists during updation. I hope you got what I mean to say. – Mehul Joisar Oct 14 '14 at 05:24
  • @PhilippeA: As you have added comment before I did, Please post your suggestion as an answer so that it can be accepted by OP if he wish to. – Mehul Joisar Oct 14 '14 at 05:26
  • @MehulJoisar yes I think you're right, it's too bad there isn't a full proof solution, but this seems like a logical one. Thanks guys – Hasam Oct 14 '14 at 05:28
  • 1
    I've added my comment as an answer, thx @mehul-joisar ;) – Philippe A Oct 14 '14 at 06:47
  • Please check my answer, it does not rely on any prior behavior of your app. – Karakuri Oct 14 '14 at 21:05
  • That won't work, because I don't want to start detecting which are new from now on, I want to be able to say that this device has had a previous version of my app, hence I will do this. Sorry if that's not clear, do read the comments on your answer if that doesn't make sense. – Hasam Oct 14 '14 at 21:09

9 Answers9

131
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;
    }
}
Rab Ross
  • 2,098
  • 22
  • 28
wudizhuo
  • 1,430
  • 1
  • 11
  • 5
  • 8
    When answering questions it is best to explain in more detail so that it can be more easily understood by other users of the site. – Tristan Dec 10 '15 at 06:40
  • this looks simpler than the accepted answer. does this work? have you tested it? – Viral Patel Jan 11 '16 at 12:00
  • 1
    Very clean solution. – jakubbialkowski Oct 04 '16 at 10:28
  • I've tested this and the firstInstallTime and lastUpdateTime is not same thing in this case. I've different results. – Djek-Grif Feb 28 '18 at 15:26
  • Wonderful solution works like a charm, Is there any way to unit test these two methods? – Ezio Apr 09 '18 at 06:15
  • 2
    For me it's not working. If i keep opening the app repeatedly the firstInstallTime and lastUpdateTime are always different. I think is not possible the lastUpdateTime and firstInstallTime to be equal, only in the case it's fresh install. – Kostadin Georgiev Jun 26 '18 at 14:43
  • 1
    Works well, but keep in mind if the user uninstalls the app and then installs it again - isFirstInstall will return true. – LachoTomov Mar 14 '19 at 13:08
  • 2
    It does not work for Android 30 and both times are same, but on lower version it works correctly. – Mahdi May 18 '22 at 08:54
24

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

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).

Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • Works perfectly! And I agree, the possibilities to circumvent this are kind of low . – Noya Jan 21 '15 at 14:16
  • version code will be the same, you can remove it from your question, it doesn't help if it wasn't been tracked from the first app version, but firstInstallTime and lastUpdateTime sounds interesting – user924 Jan 13 '22 at 11:16
7

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.

Philippe A
  • 1,252
  • 9
  • 22
  • That's good. Thanks. The worst case isn't really that bad either. – Hasam Oct 14 '14 at 06:56
  • It would be a good idea to check for any preference flag, which user might set, in order for the app to use (for eg. Terms of use agree, walkthrough etc). and also check for current app version, so that if old user comes, the flag will be set, if new user comes, the flag will be unset. – Monster Brain Jun 03 '20 at 08:52
5

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.

Vin Norman
  • 2,749
  • 1
  • 22
  • 33
4

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
Pavel Chuchuva
  • 22,633
  • 10
  • 99
  • 115
Codelaby
  • 2,604
  • 1
  • 25
  • 25
  • 1
    Thanks! It's better if 1 is used for update and 2 for normal usage. Cause then we can use `if (status < 2) { // do post-install stuff }` – Subin Sep 06 '20 at 18:29
3

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

xarlymg89
  • 2,552
  • 2
  • 27
  • 41
pjco
  • 3,826
  • 25
  • 25
  • 2
    How does this solve his problem of only enforcing the share requirement for people who did not have the previous version of the app? – Karakuri Oct 14 '14 at 05:21
  • 1
    please read the question. it's not about how to preserve the state.it's about distinguish from old version. – Mehul Joisar Oct 14 '14 at 05:21
  • Well you have to cut off the version somewhere, he hasnt been tracking who has the app installed, there is no way to magically reach into the past for that information. But using this method, you can draw a line and make the requirement going forward. But I see you point. Unless there is some distinguishing characteristic from old versions (files saved, or preferences) there is no way to know if the user updated or installed fresh. – pjco Oct 14 '14 at 05:26
  • 1
    @pjco: man, there is always a way to achieve something. May be sometimes there is no `straight forward` way. – Mehul Joisar Oct 14 '14 at 05:30
  • @MehulJoisar, I like the way you think :) Updated my answer a bit based on the feed back from you and Karakuri – pjco Oct 14 '14 at 05:35
1

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.

Daud Arfin
  • 2,499
  • 1
  • 18
  • 37
  • 8
    after updating the app, it will always return new version. so by using above approach, there is no way to distinguish whether it is updated or fresh install. – Mehul Joisar Oct 14 '14 at 05:05
  • he knows from which version he want to restrict user i mean the older version name and code .. dat he need to compare dats all – Daud Arfin Oct 14 '14 at 05:07
  • 2
    Please read the question again, he does not want to restrict all the users of new version, he wants to restrict only the new users. – Mehul Joisar Oct 14 '14 at 05:12
  • I know this is an old answer, but then, Daud is right. You can use a boolean flag (in SharedPreferences) to detect if the user is a new user of the current version. Just mark it false if the immediately the action is performed. – Taslim Oseni Aug 21 '20 at 09:22
1

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.

  1. Increase the versionCode in your app-level build.gradle (so it counts as an update).
  2. Click Build -> Build Bundle(s) / APK(s) -> Build APK(s), and select a debug APK.
  3. Run following command in the terminal of Android Studio:

    adb install -r C:\Repositories\updatelistener\app\build\outputs\apk\debug\app-debug.apk
    
Shahbaz Hashmi
  • 2,631
  • 2
  • 26
  • 49
0

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;
    }

}

amoljdv06
  • 2,646
  • 1
  • 13
  • 18