创建一个在固定时间段后过期的 Android 试用应用程序

发布于 2024-07-25 04:17:58 字数 80 浏览 5 评论 0原文

我有一个应用程序,我想将其作为付费应用程序投放市场。 我想要其他版本,即“试用”版本,时间限制为 5 天?

我该怎么做呢?

I have an application which I want to hit the market as a Paid app. I would like to have other version which would be a "trial" version with a time limit of say, 5 days?

How can I go about doing this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

尴尬癌患者 2024-08-01 04:17:59

目前,大多数开发人员使用以下 3 种技术之一来实现此目的。

第一种方法很容易规避,第一次运行应用程序时,将日期/时间保存到文件、数据库或共享首选项中,然后每次运行应用程序时检查试用期是否已结束。 这很容易规避,因为卸载并重新安装将使用户有另一个试用期。

第二种方法更难规避,但仍然可以规避。 使用硬编码定时炸弹。 基本上,通过这种方法,您将硬编码试用的结束日期,并且下载和使用该应用程序的所有用户将同时停止使用该应用程序。 我使用这种方法是因为它很容易实现,而且在大多数情况下我只是不想经历第三种技术的麻烦。 用户可以通过手动更改手机上的日期来规避此问题,但大多数用户不会费力去做这样的事情。

第三种技巧是我听说的真正能够完成你想做的事情的唯一方法。 您必须设置一个服务器,然后每当您的应用程序启动时,您的应用程序都会发送 电话到服务器的唯一标识符。 如果服务器没有该电话 ID 的条目,则会创建一个新条目并记录时间。 如果服务器确实有电话 ID 的条目,那么它会进行简单的检查以查看试用期是否已过期。 然后,它将试用到期检查的结果传达回您的应用程序。 这种方法应该是不可规避的,但确实需要设置网络服务器等。

在 onCreate 中执行这些检查始终是一个好习惯。 如果过期已结束,则会弹出一个 AlertDialog,其中包含完整的 市场链接应用程序的版本。 仅包含“确定”按钮,一旦用户单击“确定”,就调用“finish()”来结束活动。

Currently most developers accomplish this using one of the following 3 techniques.

The first approach is easily circumvented, the first time you run the app save the date/time to a file, database, or shared preferences and every time you run the app after that check to see if the trial period has ended. This is easy to circumvent because uninstalling and reinstalling will allow the user to have another trial period.

The second approach is harder to circumvent, but still circumventable. Use a hard coded time bomb. Basically with this approach you will be hard code an end date for the trial, and all users that download and use the app will stop being able to use the app at the same time. I have used this approach because it is easy to implement and for the most part I just didn't feel like going through the trouble of the third technique. Users can circumvent this by manually changing the date on their phone, but most users won't go through the trouble to do such a thing.

The third technique is the only way that I have heard about to truly be able to accomplish what you want to do. You will have to set up a server, and then whenever your application is started your app sends the phones unique identifier to the server. If the server does not have an entry for that phone id then it makes a new one and notes the time. If the server does have an entry for the phone id then it does a simple check to see if the trial period has expired. It then communicates the results of the trial expiration check back to your application. This approach should not be circumventable, but does require setting up a webserver and such.

It is always good practice to do these checks in the onCreate. If the expiration has ended popup an AlertDialog with a market link to the full version of the app. Only include an "OK" button, and once the user clicks on "OK" make a call to "finish()" to end the activity.

深海少女心 2024-08-01 04:17:59

我开发了一个 Android Trial SDK,您只需将其放入您的 Android Studio 项目中,它就会处理所有问题为您提供服务器端管理(包括离线宽限期)。

要使用它,只需

将库添加到主模块的build.gradle

dependencies {
  compile 'io.trialy.library:trialy:1.0.2'
}

在主活动的onCreate()方法中初始化库

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Initialize the library and check the current trial status on every launch
    Trialy mTrialy = new Trialy(mContext, "YOUR_TRIALY_APP_KEY");
    mTrialy.checkTrial(TRIALY_SKU, mTrialyCallback);
}

添加回调处理程序:

private TrialyCallback mTrialyCallback = new TrialyCallback() {
    @Override
    public void onResult(int status, long timeRemaining, String sku) {
        switch (status){
            case STATUS_TRIAL_JUST_STARTED:
                //The trial has just started - enable the premium features for the user
                 break;
            case STATUS_TRIAL_RUNNING:
                //The trial is currently running - enable the premium features for the user
                break;
            case STATUS_TRIAL_JUST_ENDED:
                //The trial has just ended - block access to the premium features
                break;
            case STATUS_TRIAL_NOT_YET_STARTED:
                //The user hasn't requested a trial yet - no need to do anything
                break;
            case STATUS_TRIAL_OVER:
                //The trial is over
                break;
        }
        Log.i("TRIALY", "Trialy response: " + Trialy.getStatusMessage(status));
    }

};

要开始试用,请调用 mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback);
您的应用密钥和试用 SKU 可以在您的试用开发者仪表板中找到。

I've developed a Android Trial SDK which you can simply drop into your Android Studio project and it will take care of all the server-side management for you (including offline grace periods).

To use it, simply

Add the library to your main module's build.gradle

dependencies {
  compile 'io.trialy.library:trialy:1.0.2'
}

Initialize the library in your main activity's onCreate() method

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Initialize the library and check the current trial status on every launch
    Trialy mTrialy = new Trialy(mContext, "YOUR_TRIALY_APP_KEY");
    mTrialy.checkTrial(TRIALY_SKU, mTrialyCallback);
}

Add a callback handler:

private TrialyCallback mTrialyCallback = new TrialyCallback() {
    @Override
    public void onResult(int status, long timeRemaining, String sku) {
        switch (status){
            case STATUS_TRIAL_JUST_STARTED:
                //The trial has just started - enable the premium features for the user
                 break;
            case STATUS_TRIAL_RUNNING:
                //The trial is currently running - enable the premium features for the user
                break;
            case STATUS_TRIAL_JUST_ENDED:
                //The trial has just ended - block access to the premium features
                break;
            case STATUS_TRIAL_NOT_YET_STARTED:
                //The user hasn't requested a trial yet - no need to do anything
                break;
            case STATUS_TRIAL_OVER:
                //The trial is over
                break;
        }
        Log.i("TRIALY", "Trialy response: " + Trialy.getStatusMessage(status));
    }

};

To start a trial, call mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback);
Your app key and trial SKU can be found in your Trialy developer dashboard.

遗忘曾经 2024-08-01 04:17:59

这是一个老问题,但无论如何,也许这会对某人有所帮助。

如果您想采用最简单的方法(如果卸载/重新安装应用程序将会失败或者用户手动更改设备的日期),这可能是这样的:

private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;

@Override
protected void onCreate(Bundle state){
    SharedPreferences preferences = getPreferences(MODE_PRIVATE);
    String installDate = preferences.getString("InstallDate", null);
    if(installDate == null) {
        // First run, so save the current date
        SharedPreferences.Editor editor = preferences.edit();
        Date now = new Date();
        String dateString = formatter.format(now);
        editor.putString("InstallDate", dateString);
        // Commit the edits!
        editor.commit();
    }
    else {
        // This is not the 1st run, check install date
        Date before = (Date)formatter.parse(installDate);
        Date now = new Date();
        long diff = now.getTime() - before.getTime();
        long days = diff / ONE_DAY;
        if(days > 30) { // More than 30 days?
             // Expired !!!
        }
    }

    ...
}

This is an old question but anyways, maybe this will help someone.

In case you want to go with the most simplistic approach(which will fail if the app is uninstalled/reinstalled or user changes device's date manually), this is how it could be:

private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;

@Override
protected void onCreate(Bundle state){
    SharedPreferences preferences = getPreferences(MODE_PRIVATE);
    String installDate = preferences.getString("InstallDate", null);
    if(installDate == null) {
        // First run, so save the current date
        SharedPreferences.Editor editor = preferences.edit();
        Date now = new Date();
        String dateString = formatter.format(now);
        editor.putString("InstallDate", dateString);
        // Commit the edits!
        editor.commit();
    }
    else {
        // This is not the 1st run, check install date
        Date before = (Date)formatter.parse(installDate);
        Date now = new Date();
        long diff = now.getTime() - before.getTime();
        long days = diff / ONE_DAY;
        if(days > 30) { // More than 30 days?
             // Expired !!!
        }
    }

    ...
}
鸩远一方 2024-08-01 04:17:59

这个问题和 snctln 的答案启发我将基于方法 3 的解决方案作为我的学士论文。 我知道当前状态不适用于生产性用途,但我很想听听您的想法! 你会使用这样的系统吗? 您希望将其视为云服务(配置服务器时不会遇到问题)吗? 担心安全问题或稳定性原因?

当我完成学士程序后,我想继续开发该软件。 所以现在是我需要您的反馈的时候了!

源代码托管在 GitHub https://github.com/MaChristmann/mobile-Trial

一些信息关于系统:
- 该系统由三个部分组成:一个 Android 库、一个 Node.js 服务器和一个用于管理多个试用应用程序和发布者/开发者帐户的配置器。

  • 它仅支持基于时间的试用,并且使用您的(Play 商店或其他)帐户而不是手机 ID。

    它仅支持基于时间的试用,并且

  • 对于 Android 库,它基于 Google Play 许可验证库。 我修改了它以连接到 node.js 服务器,此外该库还尝试识别用户是否更改了系统日期。 它还将检索到的试用许可证缓存在 AES 加密的共享首选项中。 您可以使用配置器配置缓存的有效时间。 如果用户“清除数据”,库将强制进行服务器端检查。

  • 服务器正在使用 https 并对许可证检查响应进行数字签名。 它还具有用于 CRUD 试用应用程序和用户(发布商和开发人员)的 API。 与许可验证库类似,开发人员可以使用测试结果在试用应用程序中测试其行为实现。 因此,您可以在配置器中将许可证响应显式设置为“已许可”、“未许可”或“服务器错误”。

  • 如果您使用一项令人惊叹的新功能更新您的应用,您可能希望每个人都可以再次尝试。 在配置器中,您可以通过设置触发此操作的版本代码来为许可证过期的用户续订试用许可证。 例如,用户在版本代码 3 上运行您的应用程序,而您希望他尝试版本代码 4 的功能。如果他更新应用程序或重新安装应用程序,他可以再次使用完整试用期,因为服务器知道他上次尝试的是哪个版本

  • 一切都在 Apache 2.0 许可证下

This question and the answer of snctln inspired me to work on a solution based on method 3 as my bachelor thesis. I know the current status is not for productive usage but I would love to hear what you think about it! Would you use such a system? Would you like to see it as a cloud service (not having trouble with configuring a server)? Concerned about security issues or stability reasons?

A soon as I finished the bachelor procedure I want to continue working on the software. So now its the time I need your feedback!

Sourcecode is hosted on GitHub https://github.com/MaChristmann/mobile-trial

Some information about the system:
- The system has three parts, a Android library, a node.js server and a configurator for managing multiple trial apps and publisher/developer accounts.

  • It only supports time-based trials and it uses your (play store or other) account rather than a phone ID.

  • For Android library it is based on the Google Play licensing verification library. I modified it to connect to the node.js server and additionally the library tries to recognize if a user changed the system date. It also caches a retrieved trial-license in AES encrypted Shared Preferences. You can configure the valid time of the cache with the configurator. If a user "clear data" the library will force a server-side check.

  • Server is using https and also digital signing the license-check response. It has also an API for CRUD trial apps and users (publisher and developer). Similiar to Licensing Verfication Library developers can test their behaviour implementation in the trial app with test result. So you in the configurator you can explicit set your license response to "licensed", "not licensed" or "server error".

  • If you update your app with an ass-kicking new feature you might want that everyone can try it again. In the configurator you can renew the trial license for users with expired licenses by setting a versioncode that should trigger this. For example user is running your app on versioncode 3 und you want him to try features of versioncode 4. If he updates the app or reinstall it he is able to use full trial period again because the server knows on which version he has tried it last time.

  • Everything is under the Apache 2.0 license

╄→承喏 2024-08-01 04:17:59

最简单且最好的方法是实施 BackupSharedPreferences。

即使卸载并重新安装应用程序,首选项也会保留。

只需将安装日期保存为首选项即可。

这是理论:
http://developer.android.com/reference/android/app/ backup/SharedPreferencesBackupHelper.html

这是示例:
Android SharedPreferences 备份不起作用

The easiest and best way to do this is the implement BackupSharedPreferences.

The preferences are preserved, even if the app is uninstalled and reinstalled.

Simply save the install date as a preference and you are good to go.

Here's the theory:
http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

Here's the example:
Android SharedPreferences Backup Not Working

月亮坠入山谷 2024-08-01 04:17:59

方法 4:使用应用程序安装时间。

自 API 级别 9(Android 2.3.2、2.3.1、Android 2.3、GINGERBREAD)以来,有 firstInstallTimelastUpdateTimePackageInfo 中。

要阅读更多内容:
如何从 android 获取应用安装时间

Approach 4: use the application install time.

Since API level 9 (Android 2.3.2, 2.3.1, Android 2.3, GINGERBREAD) there are firstInstallTime and lastUpdateTime in PackageInfo.

To read more:
How to get app install time from android

轻拂→两袖风尘 2024-08-01 04:17:59

查看此线程和其他线程中的所有选项后,这些是我的发现

共享首选项、数据库
可以在安卓设置中清除,应用程序重新安装后会丢失。 可以用android的备份机制进行备份并且会被恢复重新安装后。 备份可能并不总是可用,但在大多数设备上应该可用

外部存储(写入文件)
如果我们不这样做,则不受设置清除或重新安装的影响不写入应用程序的私有目录。 但是:在较新的 Android 版本中,需要您在运行时询问用户的许可,因此,这可能只有在您无论如何都需要该许可的情况下才可行。 也可以备份。

PackageInfo.firstInstallTime
重新安装后重置,但在更新后保持稳定

登录某个帐户
无论是他们通过 Firebase 的 Google 帐户还是您自己的服务器中的帐户都没关系:试用版与该帐户绑定。 创建新帐户将重置试用期。

Firebase 匿名登录
您可以匿名登录用户并将其数据存储在 Firebase 中。 但是显然重新安装应用程序以及其他未记录的事件可能会给用户一个新的匿名ID,重置他们的试用时间。 (Google 本身并未提供太多相关文档)

ANDROID_ID
可能不可用,并且在某些情况下可能会发生变化,例如恢复出厂设置。 关于使用它来识别设备是否是个好主意的观点似乎不同。

播放广告 ID
可由用户重置。 用户可以通过选择退出广告跟踪来禁用。< /a>

实例ID
重新安装时重置发生安全事件时重置。 可以由您的应用程序重置。

哪种方法(组合)适合您取决于您​​的应用程序以及您认为普通约翰会投入多少努力来获得另一个试用期。 我建议避免使用匿名 Firebase 和广告 ID,因为它们不稳定。 多因素方法似乎会产生最佳结果。 您可以使用哪些因素取决于您的应用程序及其权限。

对于我自己的应用程序,我发现共享首选项+firstInstallTime+首选项备份是侵入性最小但也足够有效的方法。 您必须确保仅在检查试用开始时间并将其存储在共享首选项中后才请求备份。 共享首选项中的值必须优先于firstInstallTime。 然后用户必须重新安装应用程序,运行一次,然后清除应用程序的数据以重置试用版,这是相当大量的工作。 不过,在没有备份传输的设备上,用户只需重新安装即可重置试用版。

我已将这种方法作为可扩展库提供。

After looking at all options in this and other threads, these are my findings

Shared preferences, database
Can be cleared in the android settings, lost after an app reinstall. Can be backed up with android's backup mechanism and will be restored after a reinstall. Backup may not always be available, though should be on most devices

External storage (writing to a file)
Not affected by a clear from the settings or a reinstall if we don't write to the application's private directory. But: requires you to ask the user for their permission at runtime in newer android versions, so this is probably only feasible if you need that permission anyways. Can also be backed up.

PackageInfo.firstInstallTime
Is reset after a reinstall but stable across updates

Sign in to some account
Doesn't matter if it's their Google account via Firebase or one in your own server: the trial is bound to the account. Making a new account will reset the trial.

Firebase anonymous sign in
You can sign in a user anonymously and store data for them in Firebase. But apparently a reinstall of the app and maybe other undocumented events may give the user a new anonymous ID, resetting their trial time. (Google themselves don't provide much documentation on this)

ANDROID_ID
May not be available and may change under certain circumstances, e.g factory reset. The opinions on whether it's a good idea to use this to identify devices seem to differ.

Play Advertising ID
May be reset by the user. May be disabled by the user by opting out of ad tracking.

InstanceID
Reset on a reinstall. Reset in case of a security event. Can be reset by your app.

Which (combination of) methods work for you depends on your app and on how much effort you think the average John will put into gaining another trial period. I would recommend steering clear of using only anonymous Firebase and Advertising ID due to their instability. A multi-factor approach seems like it will yield the best results. Which factors are available to you depends on you app and its permissions.

For my own app I found shared preferences + firstInstallTime + backup of the preferences to be the least intrusive but also effective enough method. You have to make sure you only request a backup after checking and storing the trial start time in the shared preferences. Values in the shared Prefs must have precedence over the firstInstallTime. Then user has to reinstall the app, run it once and then clear the app's data to reset the trial, which is quite a lot of work. On devices without a backup transport the user can reset the trial by simply reinstalling, though.

I've made that approach available as an extensible library.

口干舌燥 2024-08-01 04:17:59

现在在最新版本的android中添加了免费试用订阅,您只有在应用程序内购买订阅免费试用期后才能解锁应用程序的所有功能。
这将让用户在试用期内使用您的应用程序,如果试用期后应用程序仍然被卸载,那么订阅费将转移给您。
我没有尝试过,只是分享一个想法。

这里是文档

Now in the recent version of android free trial subscription has been added, you can unlock all your app's features only after buying the subscription within app for a free trial period.
This will let the user to use your app for a trial period , if the app is still uninstalled after the trial period then the subscription money will be transferred to you.
I have not tried , but just sharing an idea.

Here's documentation

债姬 2024-08-01 04:17:59

在我看来,最好的方法就是简单地使用 Firebase 实时数据库:

1) 将 Firebase 支持添加到您的应用程序

2) 选择“匿名身份验证”,这样用户就不必注册,甚至不需要知道您的身份正在做。 这保证链接到当前经过身份验证的用户帐户,因此可以跨设备工作。

3) 使用实时数据库 API 设置“installed_date”的值。 在启动时,只需检索该值并使用它即可。

我也做了同样的事情并且效果很好。 我能够在卸载/重新安装时对此进行测试,并且实时数据库中的值保持不变。 这样,您的试用期就可以跨多个用户设备运行。 您甚至可以对 install_date 进行版本控制,以便应用程序“重置”每个新主要版本的试用日期。

更新:经过更多测试后,似乎匿名 Firebase 似乎会分配不同的 ID,以防您拥有不同的设备,并且在重新安装之间无法保证:/唯一有保证的方法是使用Firebase 但将其绑定到他们的谷歌帐户。 这应该可行,但需要额外的步骤,用户首先需要登录/注册。

到目前为止,我最终采用了一种稍微不太优雅的方法,即简单地检查备份的首选项和安装时存储在首选项中的日期。 这适用于以数据为中心的应用程序,人们重新安装应用程序并重新输入之前添加的所有数据是毫无意义的,但不适用于简单的游戏。

In my opinion, the best way to do this is to simply use the Firebase Realtime Database:

1) Add Firebase support to your app

2) Select 'Anonymous authentication' so that the user doesn't have to signup or even know what you're doing. This is guaranteed to link to the currently authenticated user account and so will work across devices.

3) Use the Realtime Database API to set a value for 'installed_date'. At launch time, simply retrieve this value and use this.

I've done the same and it works great. I was able to test this across uninstall / re-installs and the value in the realtime database remains the same. This way your trial period works across multiple user devices. You can even version your install_date so that the app 'resets' the Trial date for each new major release.

UPDATE: After testing a bit more, it seems anonymous Firebase seems to allocate a different ID in case you've got different devices and is not guaranteed between re-installs :/ The only guaranteed way is to use Firebase but tie it to their google account. This should work, but would require an extra step where the user first needs to login / signup.

I've thus far ended up with a slightly less elegant approach of simply checking against backed-up preferences and a date stored in preferences upon install. This works for data-centric apps where it's pointless for a person to re-install the app and re-enter all the data previously added, but would not work for a simple game.

萌辣 2024-08-01 04:17:59

根据定义,市场上所有付费Android应用程序都可以在购买后24小时内进行评估。

有一个“卸载并退款”按钮,24 小时后会更改为“卸载”。

我认为这个按钮太突出了!

By definition, all paid Android apps on the market can be evaluated for 24 hours after purchase.

There's an 'Uninstall and Refund' button which changes to 'Uninstall' after 24 hours.

I'd argue this button is way too prominent!

甜点 2024-08-01 04:17:59

我在搜索同样的问题时遇到了这个问题,我认为我们可以利用免费的日期API,例如 http:// www.timeapi.org/utc/now 或其他一些日期 API 来检查跟踪应用程序是否过期。 如果您希望交付演示并担心付款并需要修复终身演示,则这种方式非常有效。 :)

下面的代码......

public class ValidationActivity extends BaseMainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

@Override
protected void onResume() {
    processCurrentTime();
    super.onResume();
}

private void processCurrentTime() {
    if (!isDataConnectionAvailable(ValidationActivity.this)) {
        showerrorDialog("No Network coverage!");
    } else {
        String urlString = "http://api.timezonedb.com/?zone=Europe/London&key=OY8PYBIG2IM9";
        new CallAPI().execute(urlString);
    }
}

private void showerrorDialog(String data) {
    Dialog d = new Dialog(ValidationActivity.this);
    d.setTitle("LS14");
    TextView tv = new TextView(ValidationActivity.this);
    tv.setText(data);
    tv.setPadding(20, 30, 20, 50);
    d.setContentView(tv);
    d.setOnDismissListener(new OnDismissListener() {
        @Override
        public void onDismiss(DialogInterface dialog) {
            finish();
        }
    });
    d.show();
}

private void checkExpiry(int isError, long timestampinMillies) {
    long base_date = 1392878740000l;// feb_19 13:8 in GMT;
    // long expiryInMillies=1000*60*60*24*5;
    long expiryInMillies = 1000 * 60 * 10;
    if (isError == 1) {
        showerrorDialog("Server error, please try again after few seconds");
    } else {
        System.out.println("fetched time " + timestampinMillies);
        System.out.println("system time -" + (base_date + expiryInMillies));
        if (timestampinMillies > (base_date + expiryInMillies)) {
            showerrorDialog("Demo version expired please contact vendor support");
            System.out.println("expired");
        }
    }
}

private class CallAPI extends AsyncTask<String, String, String> {
    @Override
    protected void onPreExecute() {
        // TODO Auto-generated method stub
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(String... params) {
        String urlString = params[0]; // URL to call
        String resultToDisplay = "";
        InputStream in = null;
        // HTTP Get
        try {
            URL url = new URL(urlString);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream());
            resultToDisplay = convertStreamToString(in);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return e.getMessage();
        }
        return resultToDisplay;
    }

    protected void onPostExecute(String result) {
        int isError = 1;
        long timestamp = 0;
        if (result == null || result.length() == 0 || result.indexOf("<timestamp>") == -1 || result.indexOf("</timestamp>") == -1) {
            System.out.println("Error $$$$$");
        } else {
            String strTime = result.substring(result.indexOf("<timestamp>") + 11, result.indexOf("</timestamp>"));
            System.out.println(strTime);
            try {
                timestamp = Long.parseLong(strTime) * 1000;
                isError = 0;
            } catch (NumberFormatException ne) {
            }
        }
        checkExpiry(isError, timestamp);
    }

} // end CallAPI

public static boolean isDataConnectionAvailable(Context context) {
    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo info = connectivityManager.getActiveNetworkInfo();
    if (info == null)
        return false;

    return connectivityManager.getActiveNetworkInfo().isConnected();
}

public String convertStreamToString(InputStream is) throws IOException {
    if (is != null) {
        Writer writer = new StringWriter();

        char[] buffer = new char[1024];
        try {
            Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            int n;
            while ((n = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, n);
            }
        } finally {
            is.close();
        }
        return writer.toString();
    } else {
        return "";
    }
}

@Override
public void onClick(View v) {
    // TODO Auto-generated method stub

}
}

找到其工作解决方案

I come across this question while searching for the same problem, i think we can utilize free date api like http://www.timeapi.org/utc/now or some other date api to check for expiry of trail app. this way is efficient if you wish to deliver the demo and worried about payment and require fix tenure demo. :)

find the code below

public class ValidationActivity extends BaseMainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

@Override
protected void onResume() {
    processCurrentTime();
    super.onResume();
}

private void processCurrentTime() {
    if (!isDataConnectionAvailable(ValidationActivity.this)) {
        showerrorDialog("No Network coverage!");
    } else {
        String urlString = "http://api.timezonedb.com/?zone=Europe/London&key=OY8PYBIG2IM9";
        new CallAPI().execute(urlString);
    }
}

private void showerrorDialog(String data) {
    Dialog d = new Dialog(ValidationActivity.this);
    d.setTitle("LS14");
    TextView tv = new TextView(ValidationActivity.this);
    tv.setText(data);
    tv.setPadding(20, 30, 20, 50);
    d.setContentView(tv);
    d.setOnDismissListener(new OnDismissListener() {
        @Override
        public void onDismiss(DialogInterface dialog) {
            finish();
        }
    });
    d.show();
}

private void checkExpiry(int isError, long timestampinMillies) {
    long base_date = 1392878740000l;// feb_19 13:8 in GMT;
    // long expiryInMillies=1000*60*60*24*5;
    long expiryInMillies = 1000 * 60 * 10;
    if (isError == 1) {
        showerrorDialog("Server error, please try again after few seconds");
    } else {
        System.out.println("fetched time " + timestampinMillies);
        System.out.println("system time -" + (base_date + expiryInMillies));
        if (timestampinMillies > (base_date + expiryInMillies)) {
            showerrorDialog("Demo version expired please contact vendor support");
            System.out.println("expired");
        }
    }
}

private class CallAPI extends AsyncTask<String, String, String> {
    @Override
    protected void onPreExecute() {
        // TODO Auto-generated method stub
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(String... params) {
        String urlString = params[0]; // URL to call
        String resultToDisplay = "";
        InputStream in = null;
        // HTTP Get
        try {
            URL url = new URL(urlString);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream());
            resultToDisplay = convertStreamToString(in);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return e.getMessage();
        }
        return resultToDisplay;
    }

    protected void onPostExecute(String result) {
        int isError = 1;
        long timestamp = 0;
        if (result == null || result.length() == 0 || result.indexOf("<timestamp>") == -1 || result.indexOf("</timestamp>") == -1) {
            System.out.println("Error $$$$$");
        } else {
            String strTime = result.substring(result.indexOf("<timestamp>") + 11, result.indexOf("</timestamp>"));
            System.out.println(strTime);
            try {
                timestamp = Long.parseLong(strTime) * 1000;
                isError = 0;
            } catch (NumberFormatException ne) {
            }
        }
        checkExpiry(isError, timestamp);
    }

} // end CallAPI

public static boolean isDataConnectionAvailable(Context context) {
    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo info = connectivityManager.getActiveNetworkInfo();
    if (info == null)
        return false;

    return connectivityManager.getActiveNetworkInfo().isConnected();
}

public String convertStreamToString(InputStream is) throws IOException {
    if (is != null) {
        Writer writer = new StringWriter();

        char[] buffer = new char[1024];
        try {
            Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            int n;
            while ((n = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, n);
            }
        } finally {
            is.close();
        }
        return writer.toString();
    } else {
        return "";
    }
}

@Override
public void onClick(View v) {
    // TODO Auto-generated method stub

}
}

its working solution.....

倾城花音 2024-08-01 04:17:59

这是我的做法,
我创建了 2 个应用程序,一个具有试用活动,另一个没有,

我将一个没有试用活动的应用程序作为付费应用程序上传到 Play 商店,

而将一个具有试用活动的应用程序作为免费应用程序上传。

首次推出的免费应用程序提供试用和商店购买选项,
如果用户选择商店购买,它将重定向到商店供用户购买
但如果用户点击试用,则会将他们带到试用活动

注意:我使用了选项 3,例如@snctln,但经过修改

首先,我不依赖于设备时间,我从 php 获取时间向数据库进行试用注册的文件,

其次,我使用设备序列号来唯一标识每个设备,

最后,应用程序取决于从服务器连接不是自己的时间,所以只有更改设备序列号才能绕过系统,这对用户来说压力很大。

所以这是我的代码(对于试用活动):

package com.example.mypackage.my_app.Start_Activity.activity;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.example.onlinewisdom.cbn_app.R;
import com.example.mypackage.my_app.Start_Activity.app.Config;
import com.example.mypackage.my_app.Start_Activity.data.TrialData;
import com.example.mypackage.my_app.Start_Activity.helper.connection.Connection;
import com.google.gson.Gson;

import org.json.JSONObject;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import cn.pedant.SweetAlert.SweetAlertDialog;

public class Trial extends AppCompatActivity {
    Connection check;
    SweetAlertDialog pDialog;
    TextView tvPleaseWait;
    private static final int MY_PERMISSIONS_REQUEST_READ_PHONE_STATE = 0;

    String BASE_URL = Config.BASE_URL;
    String BASE_URL2 = BASE_URL+ "/register_trial/"; //http://ur link to ur API

    //KEY
    public static final String KEY_IMEI = "IMEINumber";

    private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
    private final long ONE_DAY = 24 * 60 * 60 * 1000;

    SharedPreferences preferences;
    String installDate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_trial);

        preferences = getPreferences(MODE_PRIVATE);
        installDate = preferences.getString("InstallDate", null);

        pDialog = new SweetAlertDialog(this, SweetAlertDialog.PROGRESS_TYPE);
        pDialog.getProgressHelper().setBarColor(Color.parseColor("#008753"));
        pDialog.setTitleText("Loading...");
        pDialog.setCancelable(false);

        tvPleaseWait = (TextView) findViewById(R.id.tvPleaseWait);
        tvPleaseWait.setText("");

        if(installDate == null) {
            //register app for trial
            animateLoader(true);
            CheckConnection();
        } else {
            //go to main activity and verify there if trial period is over
            Intent i = new Intent(Trial.this, MainActivity.class);
            startActivity(i);
            // close this activity
            finish();
        }

    }

    public void CheckConnection() {
        check = new Connection(this);
        if (check.isConnected()) {
            //trigger 'loadIMEI'
            loadIMEI();
        } else {
            errorAlert("Check Connection", "Network is not detected");
            tvPleaseWait.setText("Network is not detected");
            animateLoader(false);
        }
    }

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        //Changes 'back' button action
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            finish();
        }
        return true;
    }

    public void animateLoader(boolean visibility) {
        if (visibility)
            pDialog.show();
        else
            pDialog.hide();
    }

    public void errorAlert(String title, String msg) {
        new SweetAlertDialog(this, SweetAlertDialog.ERROR_TYPE)
                .setTitleText(title)
                .setContentText(msg)
                .show();
    }

    /**
     * Called when the 'loadIMEI' function is triggered.
     */
    public void loadIMEI() {
        // Check if the READ_PHONE_STATE permission is already available.
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) {
            // READ_PHONE_STATE permission has not been granted.
            requestReadPhoneStatePermission();
        } else {
            // READ_PHONE_STATE permission is already been granted.
            doPermissionGrantedStuffs();
        }
    }


    /**
     * Requests the READ_PHONE_STATE permission.
     * If the permission has been denied previously, a dialog will prompt the user to grant the
     * permission, otherwise it is requested directly.
     */
    private void requestReadPhoneStatePermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.READ_PHONE_STATE)) {
            // Provide an additional rationale to the user if the permission was not granted
            // and the user would benefit from additional context for the use of the permission.
            // For example if the user has previously denied the permission.
            new AlertDialog.Builder(Trial.this)
                    .setTitle("Permission Request")
                    .setMessage(getString(R.string.permission_read_phone_state_rationale))
                    .setCancelable(false)
                    .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            //re-request
                            ActivityCompat.requestPermissions(Trial.this,
                                    new String[]{Manifest.permission.READ_PHONE_STATE},
                                    MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
                        }
                    })
                    .setIcon(R.drawable.warning_sigh)
                    .show();
        } else {
            // READ_PHONE_STATE permission has not been granted yet. Request it directly.
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE},
                    MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
        }
    }

    /**
     * Callback received when a permissions request has been completed.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if (requestCode == MY_PERMISSIONS_REQUEST_READ_PHONE_STATE) {
            // Received permission result for READ_PHONE_STATE permission.est.");
            // Check if the only required permission has been granted
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // READ_PHONE_STATE permission has been granted, proceed with displaying IMEI Number
                //alertAlert(getString(R.string.permision_available_read_phone_state));
                doPermissionGrantedStuffs();
            } else {
                alertAlert(getString(R.string.permissions_not_granted_read_phone_state));
            }
        }
    }

    private void alertAlert(String msg) {
        new AlertDialog.Builder(Trial.this)
                .setTitle("Permission Request")
                .setMessage(msg)
                .setCancelable(false)
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // do somthing here
                    }
                })
                .setIcon(R.drawable.warning_sigh)
                .show();
    }

    private void successAlert(String msg) {
        new SweetAlertDialog(this, SweetAlertDialog.SUCCESS_TYPE)
                .setTitleText("Success")
                .setContentText(msg)
                .setConfirmText("Ok")
                .setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
                    @Override
                    public void onClick(SweetAlertDialog sDialog) {
                        sDialog.dismissWithAnimation();
                        // Prepare intent which is to be triggered
                        //Intent i = new Intent(Trial.this, MainActivity.class);
                        //startActivity(i);
                    }
                })
                .show();
    }

    public void doPermissionGrantedStuffs() {
        //Have an  object of TelephonyManager
        TelephonyManager tm =(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
        //Get IMEI Number of Phone  //////////////// for this example i only need the IMEI
        String IMEINumber = tm.getDeviceId();

        /************************************************
         * **********************************************
         * This is just an icing on the cake
         * the following are other children of TELEPHONY_SERVICE
         *
         //Get Subscriber ID
         String subscriberID=tm.getDeviceId();

         //Get SIM Serial Number
         String SIMSerialNumber=tm.getSimSerialNumber();

         //Get Network Country ISO Code
         String networkCountryISO=tm.getNetworkCountryIso();

         //Get SIM Country ISO Code
         String SIMCountryISO=tm.getSimCountryIso();

         //Get the device software version
         String softwareVersion=tm.getDeviceSoftwareVersion()

         //Get the Voice mail number
         String voiceMailNumber=tm.getVoiceMailNumber();


         //Get the Phone Type CDMA/GSM/NONE
         int phoneType=tm.getPhoneType();

         switch (phoneType)
         {
         case (TelephonyManager.PHONE_TYPE_CDMA):
         // your code
         break;
         case (TelephonyManager.PHONE_TYPE_GSM)
         // your code
         break;
         case (TelephonyManager.PHONE_TYPE_NONE):
         // your code
         break;
         }

         //Find whether the Phone is in Roaming, returns true if in roaming
         boolean isRoaming=tm.isNetworkRoaming();
         if(isRoaming)
         phoneDetails+="\nIs In Roaming : "+"YES";
         else
         phoneDetails+="\nIs In Roaming : "+"NO";


         //Get the SIM state
         int SIMState=tm.getSimState();
         switch(SIMState)
         {
         case TelephonyManager.SIM_STATE_ABSENT :
         // your code
         break;
         case TelephonyManager.SIM_STATE_NETWORK_LOCKED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_PIN_REQUIRED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_PUK_REQUIRED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_READY :
         // your code
         break;
         case TelephonyManager.SIM_STATE_UNKNOWN :
         // your code
         break;

         }
         */
        // Now read the desired content to a textview.
        //tvPleaseWait.setText(IMEINumber);
        UserTrialRegistrationTask(IMEINumber);
    }

    /**
     * Represents an asynchronous login task used to authenticate
     * the user.
     */
    private void UserTrialRegistrationTask(final String IMEINumber) {
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, BASE_URL2+IMEINumber, null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        Gson gson = new Gson();
                        TrialData result = gson.fromJson(String.valueOf(response), TrialData.class);
                        animateLoader(false);
                        if ("true".equals(result.getError())) {
                            errorAlert("Error", result.getResult());
                            tvPleaseWait.setText("Unknown Error");
                        } else if ("false".equals(result.getError())) {
                            //already created install/trial_start date using the server
                            // so just getting the date called back
                            Date before = null;
                            try {
                                before = (Date)formatter.parse(result.getResult());
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                            Date now = new Date();
                            assert before != null;
                            long diff = now.getTime() - before.getTime();
                            long days = diff / ONE_DAY;
                            // save the date received
                            SharedPreferences.Editor editor = preferences.edit();
                            editor.putString("InstallDate", String.valueOf(days));
                            // Commit the edits!
                            editor.apply();
                            //go to main activity and verify there if trial period is over
                            Intent i = new Intent(Trial.this, MainActivity.class);
                            startActivity(i);
                            // close this activity
                            finish();
                            //successAlert(String.valueOf(days));
                            //if(days > 5) { // More than 5 days?
                                // Expired !!!
                            //}
                            }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        animateLoader(false);
                        //errorAlert(error.toString());
                        errorAlert("Check Connection", "Could not establish a network connection.");
                        tvPleaseWait.setText("Network is not detected");
                    }
                })

        {
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<String, String>();
                params.put(KEY_IMEI, IMEINumber);
                return params;
            }
        };

        RequestQueue requestQueue = Volley.newRequestQueue(this);
        requestQueue.add(jsonObjectRequest);
    }


}

我的 php 文件看起来像这样(它是一种 REST-slim 技术):

/**
     * registerTrial
     */
    public function registerTrial($IMEINumber) {
        //check if $IMEINumber already exist
        // Instantiate DBH
        $DBH = new PDO_Wrapper();
        $DBH->query("SELECT date_reg FROM trials WHERE device_id = :IMEINumber");
        $DBH->bind(':IMEINumber', $IMEINumber);
        // DETERMINE HOW MANY ROWS OF RESULTS WE GOT
        $totalRows_registered = $DBH->rowCount();
        // DETERMINE HOW MANY ROWS OF RESULTS WE GOT
        $results = $DBH->resultset();

        if (!$IMEINumber) {
            return 'Device serial number could not be determined.';
        } else if ($totalRows_registered > 0) {
            $results = $results[0];
            $results = $results['date_reg'];
            return $results;
        } else {
            // Instantiate variables
            $trial_unique_id = es_generate_guid(60);
            $time_reg = date('H:i:s');
            $date_reg = date('Y-m-d');

            $DBH->beginTransaction();
            // opening db connection
            //NOW Insert INTO DB
            $DBH->query("INSERT INTO trials (time_reg, date_reg, date_time, device_id, trial_unique_id) VALUES (:time_reg, :date_reg, NOW(), :device_id, :trial_unique_id)");
            $arrayValue = array(':time_reg' => $time_reg, ':date_reg' => $date_reg, ':device_id' => $IMEINumber, ':trial_unique_id' => $trial_unique_id);
            $DBH->bindArray($arrayValue);
            $subscribe = $DBH->execute();
            $DBH->endTransaction();
            return $date_reg;
        }

    }

然后在主要活动中,我使用共享首选项(在试用活动中创建的 installDate)来监视天数剩余的时间,如果时间结束,我会阻止主要活动 UI,并显示一条消息,将他们带到商店购买。

我在这里看到的唯一缺点是,如果流氓用户购买付费应用程序并决定与 Zender 等应用程序共享,文件共享甚至直接将 apk 文件托管在服务器上供人们下载自由的。 但我确信我很快就会编辑这个答案,并提供解决方案或解决方案的链接。

希望这能拯救一个灵魂……有一天

快乐编码……

Here is how i went about mine,
I created 2 apps one with trial activity the other without,

i uploaded the one without trial activity to play store as paid app,

and the one with trial activity as free app.

The free app on first launch has options for trial and store purchase,
if the user select store purchase it redirects to the store for the user to purchase
but if the user clicks trial it take them to the trial activity

NB: I used option 3 like @snctln but with modifications

first, i did not depend on the device time, i got my time from the php file that does the trial registration to the db,

secondly, i used the device serial number to uniquely identify each device,

lastly, the app depends on the time value returned from the server connection not its own time so the system can only be circumvented if the device serial number is changed, which is quite stressful for a user.

so here goes my code (for the Trial activity):

package com.example.mypackage.my_app.Start_Activity.activity;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.example.onlinewisdom.cbn_app.R;
import com.example.mypackage.my_app.Start_Activity.app.Config;
import com.example.mypackage.my_app.Start_Activity.data.TrialData;
import com.example.mypackage.my_app.Start_Activity.helper.connection.Connection;
import com.google.gson.Gson;

import org.json.JSONObject;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import cn.pedant.SweetAlert.SweetAlertDialog;

public class Trial extends AppCompatActivity {
    Connection check;
    SweetAlertDialog pDialog;
    TextView tvPleaseWait;
    private static final int MY_PERMISSIONS_REQUEST_READ_PHONE_STATE = 0;

    String BASE_URL = Config.BASE_URL;
    String BASE_URL2 = BASE_URL+ "/register_trial/"; //http://ur link to ur API

    //KEY
    public static final String KEY_IMEI = "IMEINumber";

    private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
    private final long ONE_DAY = 24 * 60 * 60 * 1000;

    SharedPreferences preferences;
    String installDate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_trial);

        preferences = getPreferences(MODE_PRIVATE);
        installDate = preferences.getString("InstallDate", null);

        pDialog = new SweetAlertDialog(this, SweetAlertDialog.PROGRESS_TYPE);
        pDialog.getProgressHelper().setBarColor(Color.parseColor("#008753"));
        pDialog.setTitleText("Loading...");
        pDialog.setCancelable(false);

        tvPleaseWait = (TextView) findViewById(R.id.tvPleaseWait);
        tvPleaseWait.setText("");

        if(installDate == null) {
            //register app for trial
            animateLoader(true);
            CheckConnection();
        } else {
            //go to main activity and verify there if trial period is over
            Intent i = new Intent(Trial.this, MainActivity.class);
            startActivity(i);
            // close this activity
            finish();
        }

    }

    public void CheckConnection() {
        check = new Connection(this);
        if (check.isConnected()) {
            //trigger 'loadIMEI'
            loadIMEI();
        } else {
            errorAlert("Check Connection", "Network is not detected");
            tvPleaseWait.setText("Network is not detected");
            animateLoader(false);
        }
    }

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        //Changes 'back' button action
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            finish();
        }
        return true;
    }

    public void animateLoader(boolean visibility) {
        if (visibility)
            pDialog.show();
        else
            pDialog.hide();
    }

    public void errorAlert(String title, String msg) {
        new SweetAlertDialog(this, SweetAlertDialog.ERROR_TYPE)
                .setTitleText(title)
                .setContentText(msg)
                .show();
    }

    /**
     * Called when the 'loadIMEI' function is triggered.
     */
    public void loadIMEI() {
        // Check if the READ_PHONE_STATE permission is already available.
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) {
            // READ_PHONE_STATE permission has not been granted.
            requestReadPhoneStatePermission();
        } else {
            // READ_PHONE_STATE permission is already been granted.
            doPermissionGrantedStuffs();
        }
    }


    /**
     * Requests the READ_PHONE_STATE permission.
     * If the permission has been denied previously, a dialog will prompt the user to grant the
     * permission, otherwise it is requested directly.
     */
    private void requestReadPhoneStatePermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.READ_PHONE_STATE)) {
            // Provide an additional rationale to the user if the permission was not granted
            // and the user would benefit from additional context for the use of the permission.
            // For example if the user has previously denied the permission.
            new AlertDialog.Builder(Trial.this)
                    .setTitle("Permission Request")
                    .setMessage(getString(R.string.permission_read_phone_state_rationale))
                    .setCancelable(false)
                    .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            //re-request
                            ActivityCompat.requestPermissions(Trial.this,
                                    new String[]{Manifest.permission.READ_PHONE_STATE},
                                    MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
                        }
                    })
                    .setIcon(R.drawable.warning_sigh)
                    .show();
        } else {
            // READ_PHONE_STATE permission has not been granted yet. Request it directly.
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE},
                    MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
        }
    }

    /**
     * Callback received when a permissions request has been completed.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if (requestCode == MY_PERMISSIONS_REQUEST_READ_PHONE_STATE) {
            // Received permission result for READ_PHONE_STATE permission.est.");
            // Check if the only required permission has been granted
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // READ_PHONE_STATE permission has been granted, proceed with displaying IMEI Number
                //alertAlert(getString(R.string.permision_available_read_phone_state));
                doPermissionGrantedStuffs();
            } else {
                alertAlert(getString(R.string.permissions_not_granted_read_phone_state));
            }
        }
    }

    private void alertAlert(String msg) {
        new AlertDialog.Builder(Trial.this)
                .setTitle("Permission Request")
                .setMessage(msg)
                .setCancelable(false)
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // do somthing here
                    }
                })
                .setIcon(R.drawable.warning_sigh)
                .show();
    }

    private void successAlert(String msg) {
        new SweetAlertDialog(this, SweetAlertDialog.SUCCESS_TYPE)
                .setTitleText("Success")
                .setContentText(msg)
                .setConfirmText("Ok")
                .setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
                    @Override
                    public void onClick(SweetAlertDialog sDialog) {
                        sDialog.dismissWithAnimation();
                        // Prepare intent which is to be triggered
                        //Intent i = new Intent(Trial.this, MainActivity.class);
                        //startActivity(i);
                    }
                })
                .show();
    }

    public void doPermissionGrantedStuffs() {
        //Have an  object of TelephonyManager
        TelephonyManager tm =(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
        //Get IMEI Number of Phone  //////////////// for this example i only need the IMEI
        String IMEINumber = tm.getDeviceId();

        /************************************************
         * **********************************************
         * This is just an icing on the cake
         * the following are other children of TELEPHONY_SERVICE
         *
         //Get Subscriber ID
         String subscriberID=tm.getDeviceId();

         //Get SIM Serial Number
         String SIMSerialNumber=tm.getSimSerialNumber();

         //Get Network Country ISO Code
         String networkCountryISO=tm.getNetworkCountryIso();

         //Get SIM Country ISO Code
         String SIMCountryISO=tm.getSimCountryIso();

         //Get the device software version
         String softwareVersion=tm.getDeviceSoftwareVersion()

         //Get the Voice mail number
         String voiceMailNumber=tm.getVoiceMailNumber();


         //Get the Phone Type CDMA/GSM/NONE
         int phoneType=tm.getPhoneType();

         switch (phoneType)
         {
         case (TelephonyManager.PHONE_TYPE_CDMA):
         // your code
         break;
         case (TelephonyManager.PHONE_TYPE_GSM)
         // your code
         break;
         case (TelephonyManager.PHONE_TYPE_NONE):
         // your code
         break;
         }

         //Find whether the Phone is in Roaming, returns true if in roaming
         boolean isRoaming=tm.isNetworkRoaming();
         if(isRoaming)
         phoneDetails+="\nIs In Roaming : "+"YES";
         else
         phoneDetails+="\nIs In Roaming : "+"NO";


         //Get the SIM state
         int SIMState=tm.getSimState();
         switch(SIMState)
         {
         case TelephonyManager.SIM_STATE_ABSENT :
         // your code
         break;
         case TelephonyManager.SIM_STATE_NETWORK_LOCKED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_PIN_REQUIRED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_PUK_REQUIRED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_READY :
         // your code
         break;
         case TelephonyManager.SIM_STATE_UNKNOWN :
         // your code
         break;

         }
         */
        // Now read the desired content to a textview.
        //tvPleaseWait.setText(IMEINumber);
        UserTrialRegistrationTask(IMEINumber);
    }

    /**
     * Represents an asynchronous login task used to authenticate
     * the user.
     */
    private void UserTrialRegistrationTask(final String IMEINumber) {
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, BASE_URL2+IMEINumber, null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        Gson gson = new Gson();
                        TrialData result = gson.fromJson(String.valueOf(response), TrialData.class);
                        animateLoader(false);
                        if ("true".equals(result.getError())) {
                            errorAlert("Error", result.getResult());
                            tvPleaseWait.setText("Unknown Error");
                        } else if ("false".equals(result.getError())) {
                            //already created install/trial_start date using the server
                            // so just getting the date called back
                            Date before = null;
                            try {
                                before = (Date)formatter.parse(result.getResult());
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                            Date now = new Date();
                            assert before != null;
                            long diff = now.getTime() - before.getTime();
                            long days = diff / ONE_DAY;
                            // save the date received
                            SharedPreferences.Editor editor = preferences.edit();
                            editor.putString("InstallDate", String.valueOf(days));
                            // Commit the edits!
                            editor.apply();
                            //go to main activity and verify there if trial period is over
                            Intent i = new Intent(Trial.this, MainActivity.class);
                            startActivity(i);
                            // close this activity
                            finish();
                            //successAlert(String.valueOf(days));
                            //if(days > 5) { // More than 5 days?
                                // Expired !!!
                            //}
                            }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        animateLoader(false);
                        //errorAlert(error.toString());
                        errorAlert("Check Connection", "Could not establish a network connection.");
                        tvPleaseWait.setText("Network is not detected");
                    }
                })

        {
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<String, String>();
                params.put(KEY_IMEI, IMEINumber);
                return params;
            }
        };

        RequestQueue requestQueue = Volley.newRequestQueue(this);
        requestQueue.add(jsonObjectRequest);
    }


}

My php file looks like this (its a REST-slim technology):

/**
     * registerTrial
     */
    public function registerTrial($IMEINumber) {
        //check if $IMEINumber already exist
        // Instantiate DBH
        $DBH = new PDO_Wrapper();
        $DBH->query("SELECT date_reg FROM trials WHERE device_id = :IMEINumber");
        $DBH->bind(':IMEINumber', $IMEINumber);
        // DETERMINE HOW MANY ROWS OF RESULTS WE GOT
        $totalRows_registered = $DBH->rowCount();
        // DETERMINE HOW MANY ROWS OF RESULTS WE GOT
        $results = $DBH->resultset();

        if (!$IMEINumber) {
            return 'Device serial number could not be determined.';
        } else if ($totalRows_registered > 0) {
            $results = $results[0];
            $results = $results['date_reg'];
            return $results;
        } else {
            // Instantiate variables
            $trial_unique_id = es_generate_guid(60);
            $time_reg = date('H:i:s');
            $date_reg = date('Y-m-d');

            $DBH->beginTransaction();
            // opening db connection
            //NOW Insert INTO DB
            $DBH->query("INSERT INTO trials (time_reg, date_reg, date_time, device_id, trial_unique_id) VALUES (:time_reg, :date_reg, NOW(), :device_id, :trial_unique_id)");
            $arrayValue = array(':time_reg' => $time_reg, ':date_reg' => $date_reg, ':device_id' => $IMEINumber, ':trial_unique_id' => $trial_unique_id);
            $DBH->bindArray($arrayValue);
            $subscribe = $DBH->execute();
            $DBH->endTransaction();
            return $date_reg;
        }

    }

then on the main activity i use the shared preference (installDate created in trial activity) to monitor the number of days remaining and if the days are over i block the main activity UI with a message that takes them to the store to purchase.

The only down side i see here is that if a Rogue user buys the paid app and decides to share with apps like Zender, file share or even host the apk file directly on a server for people to download for free. But am sure i will soon edit this answer with a solution to that or a link to the solution.

Hope this saves a soul...some day

Happy Coding...

慈悲佛祖 2024-08-01 04:17:59

@snctln 选项 3 可以轻松完成,将 php 文件添加到安装了 php 和 mysql 的 Web 服务器,就像许多服务器一样。

从 Android 端,使用 HttpURLConnection 将标识符(设备 ID、谷歌帐户或任何您想要的内容)作为 URL 中的参数传递,并且 php 返回第一次安装的日期(如果它存在于表中)或者插入新行并它返回当前日期。

这对我来说可以。

如果我有时间我会发布一些代码!

祝你好运 !

@snctln option 3 can be easily done adding a php file to a web server with php and mysql installed as many of them have.

From the Android side an identifier (the device ID, google account o whatever you want) is passed as argument in the URL using HttpURLConnection and the php returns the date of the first install if it exist in the table or it inserts a new row and it return the current date.

It works fine for me.

If I have time I will post some code !

Good Luck !

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文