一定期間後に有効期限が切れるAndroidトライアルアプリケーションを作成する


103

有料アプリとして売りたいアプリがあります。たとえば、5日間という時間制限のある「試用版」である他のバージョンが欲しいのですが?

これを行うにはどうすればよいですか?


GoogleはPlayサービスでこれを本当にサポートするべきです!
powder366 '26

@ powder366実際にはGoogleはこれをサポートしています。developer.android.com/ google / play / billing /…を参照して
Yazazzello

回答:


186

現在、ほとんどの開発者は、次の3つの手法のいずれかを使用してこれを実現しています。

最初のアプローチは簡単に回避できます。アプリを初めて実行するときは、日付、時刻をファイル、データベース、または共有設定に保存し、そのチェック後に試用期間が終了したかどうかを確認するたびにアプリを実行します。アンインストールと再インストールにより、ユーザーは別の試用期間を持つことができるため、これは簡単に回避できます。

2番目のアプローチは回避するのが困難ですが、依然として回避できます。ハードコードされた時限爆弾を使用します。基本的にこのアプローチでは、試用期間の終了日をハードコーディングし、アプリをダウンロードして使用するすべてのユーザーが同時にアプリを使用できなくなります。実装が簡単で、ほとんどの場合、3番目の手法の問題を経験したくなかったので、このアプローチを使用しました。ユーザーは自分の電話で日付を手動で変更することでこれを回避できますが、ほとんどのユーザーはそのようなことをする手間をかけません。

3番目の手法は、あなたがやりたいことを本当に実現できると私が聞いた唯一の方法です。サーバーを設定する必要があります。その後、アプリケーションが起動するたびに、アプリは電話の一意の識別子をサーバーに送信します。サーバーにその電話IDのエントリがない場合、サーバーは新しい電話IDを作成し、時刻を記録します。サーバーに電話IDのエントリがある場合、試用期間が終了したかどうかを確認するための簡単なチェックを行います。次に、試用期限チェックの結果をアプリケーションに伝えます。このアプローチは回避できないはずですが、ウェブサーバーなどを設定する必要があります。

onCreateでこれらのチェックを行うことは常に良い習慣です。有効期限が終了した場合、アプリのフルバージョンへのマーケットリンクを含むAlertDialogがポップアップします。「OK」ボタンのみを含め、ユーザーが「OK」をクリックしたら「finish()」を呼び出してアクティビティを終了します。


2
素晴らしい答え。あなたが言うように、私は2番目のオプションがおそらく最高だと思います。残念なことに、Google自体は何らかのライセンスシステムを提供していません。それは、小規模および大規模のブランドデベロッパーの両方に、さらに多くのAndroidアプリケーションの作成を奨励​​する可能性があるためです。
トム・

8
また、起動時にチェックしません。あなたの目標は、ユーザーを罰するのではなく、アプリを販売することです(それは単なるボーナスです;)実行中に2分ごとにチェックするように設定している場合、ユーザーが何かを始めて、彼らが支払うべきだと気づかせることができます。支払いを簡単にして仕事に戻ることができるようになっている場合(Androidでできるかどうかはわかりません)、onCreateの間にチェックしてから、売り上げを増やすと思います。
Whaledawg 2009年

4
@Whaledawg:サーバーはデータベースに最初の実行の電話IDと時刻を格納し、後で比較するため、独自のサーバーを実行する必要があります。また、チェックを行うときは、開発者の純粋な好みなので、ハードを使用しました。コード化された時限爆弾をゲームに組み込んで、すばらしい結果を得ました。アプリ全体が読み込まれますが、ユーザーは表示されたダイアログのみを操作できます。そのダイアログには、ユーザーをゲームの購入ページに直接移動させるボタンがあります。Androidマーケットの開設以来、ゲームは有料アプリのトップ10に入っているため、ユーザーはAFAIKを気にしていないようです。
snctln 2009年

11
追加のサーバー設定のためにオプション3を使用したくない場合は、Parse.comを参照してください。これは同期です。
Joel Skrepnek

3
トライアルのハードコード終了日とはどういう意味ですか?つまり、将来的にハードコードされた日付が異なるトライアルアプリの新しいバージョンを永久にリリースし続けるということですか。
ジャスパー

21

私は、Android Studioプロジェクトに簡単にドロップできるAndroid Trial SDKを開発しました。これにより、すべてのサーバー側の管理(オフラインの猶予期間を含む)が処理されます。

それを使用するには、単に

ライブラリをメインモジュールに追加します 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はTrialy開発者ダッシュボードにあります。


データを有効にする必要がありますか?
Sivaram Boina

裁判は信頼できません
Amir Dora

1
@AmirDeこんにちは、ミール、何がうまくいかないのか教えてください。喜んでお手伝いします。support@ trialy.io Trialyは1000人以上のユーザーに最適です
Nick

@ニックは、私のデバイスがandroid lollipopを実行している理由を知りません。ダッシュボードから日を設定すると、数分後に日が負の値で表示され、ダッシュボードにまだ何日も残っているにもかかわらず、試用期間が終了したと表示されます。私もnougatデバイスでテストしましたが、naugatでは問題なく動作しているようです。古いAndroidバージョンの互換性の問題があるかもしれません
Amir Dora

1
私はこのサービスを2016年から使用しています。毎回問題なく機能します。私の公式プロジェクトでもこれを使用しました。これは受け入れられるべき答えです。
タリクマフムード

17

これは古い質問ですが、とにかくこれは誰かを助けるでしょう。

最も単純なアプローチ(アプリをアンインストール/再インストールしたり、ユーザーがデバイスの日付を手動で変更したりすると失敗します)にしたい場合は、次のようになります。

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

    ...
}

実際、SharedPreferencesとAndroid 2.2 Froyo以上を使用している場合、Google Data SynchronizationのデータバックアップAPIを実装している限り、ユーザーはデバイス間でこれを回避したり、アンインストールしたりすることはできません。設定>アプリケーションとデータの消去。また、DateのメソッドはではありgetTimeませんgetTimeInMillis
トム

同意しません。これは、ユーザーがデバイスの日付を手動で変更した場合にも失敗し、ユーザーが手動でデータを消去した場合にもどうなりますか?
Mohammed Azharuddin Shaikh

@Canerこれはsharedprefrenceをチェックするのに適していますが、ユーザーは設定->アプリケーションマネージャからメモリをクリアして再利用します。
CoronaPintu 2013年

@CoronaPintu。このアプローチがfirebaseでも試行されるように、これは完璧な組み合わせとなり、アプリがアンインストールされても試用期間が終了します。
Noor Hossain

アプローチを「奇妙な」答えと組み合わせて追加すると、これによりアプローチが完璧になります。
Noor Hossain

10

この質問とsnctlnの答え、学士論文として方法3に基づく解決策に取り組むように私に刺激を与えました。現在の状況は生産的な使用のためのものではないことは知っていますが、あなたがそれについてどう思うか聞いてみたいです!そのようなシステムを使用しますか?クラウドサービスとして表示しますか(サーバーの構成に問題はありません)?セキュリティの問題や安定性の理由が心配ですか?

学士号の手順を終えたらすぐに、ソフトウェアの作業を続けたいと思います。だから今こそ私はあなたのフィードバックを必要としています!

ソースコードはGitHub https://github.com/MaChristmann/mobile-trialでホストされています

システムに関するいくつかの情報:-システムには、Androidライブラリ、node.jsサーバー、および複数のトライアルアプリとパブリッシャー/開発者アカウントを管理するコンフィギュレーターの3つの部分があります。

  • 時間ベースの試用版のみをサポートし、電話IDではなく(プレイストアまたはその他の)アカウントを使用します。

  • Androidライブラリの場合は、Google Playライセンス検証ライブラリに基づいています。node.jsサーバーに接続するように変更し、さらにライブラリは、ユーザーがシステム日付を変更したかどうかを認識しようとします。また、取得した試用ライセンスをAES暗号化共有設定にキャッシュします。キャッシュの有効期間は、コンフィギュレーターで構成できます。ユーザーが「データをクリア」すると、ライブラリはサーバー側のチェックを強制します。

  • サーバーはhttpsを使用しており、ライセンスチェック応答にもデジタル署名しています。また、CRUDトライアルアプリとユーザー(パブリッシャーとデベロッパー)のためのAPIも備えています。ライセンス検証ライブラリと同様に、開発者はテスト結果を使用して、トライアルアプリでの動作の実装をテストできます。したがって、コンフィギュレータで、ライセンス応答を「ライセンス済み」、「ライセンスなし」、または「サーバーエラー」に明示的に設定できます。

  • お尻を蹴るような新機能でアプリを更新する場合は、誰もがもう一度試すことができるようにしたいと思うかもしれません。コンフィギュレータで、これをトリガーするバージョンコードを設定することにより、期限切れのライセンスを持つユーザーのトライアルライセンスを更新できます。たとえば、ユーザーがバージョンコード3でアプリを実行していて、バージョンコード4の機能を試してもらいたいとします。アプリを更新するか再インストールすると、サーバーが最後に試したバージョンをサーバーが認識しているため、再び試用期間全体を使用できます。時間。

  • すべてはApache 2.0ライセンスの下にあります


2
あなたは私の日を保存しました、ハードワークに感謝します。一度取得した暗号化された構成を使用し、公開鍵をアプリケーション内に保持するという同じソリューションを書こうと思ったところです。だから私が見ることができる大きな問題は、どのようにライセンスを付与するのですか?複数の付与を回避するために、電話に固有のIDを携帯する必要がある場合があります。
user2305886 2013

6

これを行う最も簡単で最適な方法は、BackupSharedPreferencesを実装することです。

アプリがアンインストールされて再インストールされても、設定は保持されます。

インストール日付を設定として保存するだけで、問題ありません。

これが理論です:http : //developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

次に例を示します 。AndroidSharedPreferencesバックアップが機能しない


3
ユーザーはシステム設定でバックアップを無効にすることができます。
Patrick

5

アプローチ4:アプリケーションのインストール時間を使用します。

APIレベル9(Android 2.3.2、2.3.1、Android 2.3、GINGERBREAD)以降、にはfirstInstallTimelastUpdateTimeがありPackageInfoます。

続きを読む: Androidからアプリのインストール時間を取得する方法


snctlnの回答からの方法1は、簡単に回避されることなく、これで確実に使用できますか、それとも同じまたは同様の問題がありますか?
jwinn 2014年

私はこの方法をテストしました。良い面は、データがクリアされても機能することです。悪い面は、アンインストール/再インストールで回避できることです。
Jean-Philippe浄土院2014年

確認できます:Pixelでアプリをアンインストールして再インストールすると、最初のインストール時間がリセットされます。これにより、ユーザーがトライアルをリセットするのはかなり簡単になります
Fabian Streitel

3

Androidの無料トライアルサブスクリプションの最新バージョンが追加されたため、アプリ内でサブスクリプションを無料トライアル期間購入した後でのみ、アプリのすべての機能のロックを解除できます。これにより、ユーザーは試用期間中アプリを使用できます。試用期間後にアプリがまだアンインストールされている場合、サブスクリプションのお金があなたに送金されます。私は試していませんが、アイデアを共有しています。

ここにドキュメントがあります


2
これが1回の購入で機能することを願っています。サブスクリプションに対してのみ機能します。サブスクリプションは年次または月次です。
jwinn 2014年

3

私の意見では、これを行う最善の方法は、Firebase Realtime Databaseを使用することです。

1)アプリにFirebaseサポートを追加する

2)[匿名認証]を選択して、ユーザーがサインアップしたり、何をしているのかを知ったりする必要がないようにします。これは、現在認証されているユーザーアカウントにリンクすることが保証されているため、デバイス間で機能します。

3)Realtime Database APIを使用して、「installed_date」の値を設定します。起動時に、この値を取得して使用します。

私は同じことをしました、そしてそれは素晴らしい働きをします。アンインストール/再インストールでこれをテストすることができ、リアルタイムデータベースの値は同じままです。このように、試用期間は複数のユーザーデバイスで機能します。アプリが新しいメジャーリリースごとに試用日を「リセット」するように、install_dateをバージョン管理することもできます。

更新:もう少しテストした後、デバイスが異なると匿名のFirebaseが別のIDを割り当てているようで、再インストールの間に保証されません:/保証されている唯一の方法は、Firebaseを使用することですが、Googleに関連付けますアカウント。これは機能するはずですが、ユーザーが最初にログイン/サインアップする必要がある追加の手順が必要になります。

これまでのところ、バックアップされた設定とインストール時に設定に保存された日付を単にチェックするという、少し洗練されていないアプローチに終わりました。これは、ユーザーがアプリを再インストールして以前に追加したすべてのデータを再入力しても意味がないデータ中心のアプリでは機能しますが、単純なゲームでは機能しません。


私のAndroidアプリにも同じ要件があり、自分のデータベース/ウェブサーバーがあります。ユーザーはログインを必要としないので、installed_dateでデバイスIDを保存することを計画していましたが、これでうまくいくでしょうか?
user636525 2017年

@strangetimes、私はあなたの解決策が最もうまく機能すると思います、もっと情報を提供できますか?感謝
DayDayHappy

このスレッドstackoverflow.com/q/41733137/1396068は、アプリを再インストールした後、新しいユーザーID、
つまり

3

このスレッドと他のスレッドのすべてのオプションを見た後、これらは私の発見です

共有設定、データベース Android設定でクリアでき、アプリの再インストール後に失われます。Androidのバックアップメカニズムバックアップでき、再インストール後に復元されます。バックアップは常に利用できるとは限りませんが、ほとんどのデバイスで可能です

外部ストレージ(ファイルへの書き込み)アプリケーションのプライベートディレクトリに書き込み行わない場合、設定のクリアや再インストールの影響を受けません。しかし:新しいandroidバージョンでは、実行時ユーザーに許可を求める必要があるため、これはおそらく、とにかくその許可が必要な場合にのみ可能です。バックアップすることもできます。

PackageInfo.firstInstallTime は再インストール後にリセットされますが、更新全体で安定しています

一部のアカウントにサインインする Firebaseを介したGoogleアカウントであるか、自分のサーバーにあるGoogleアカウントであるかは関係ありません。試用版はアカウントにバインドされています。新しいアカウントを作成すると、トライアルがリセットされます。

Firebase匿名サインイン ユーザーを匿名でサインインして、Firebaseにユーザーのデータを保存できます。ただし、アプリの再インストールや他の文書化されていないイベントにより、ユーザーに新しい匿名IDが与えられ、試用期間がリセットされる可能性があります。(Google自身はこれに関する多くのドキュメントを提供していません)

ANDROID_ID 使用できない場合があり、出荷時設定へのリセットなど、特定の状況は変更される可能性があります。これを使用してデバイスを識別するのが良いかどうかについての意見は異なるようです。

Play広告ID ユーザーによってリセットされる場合があります。ユーザーが広告の追跡を無効にすることで無効にされる場合があります。

再インストール時にInstanceIDがリセットされます。セキュリティイベントの場合にリセットします。アプリでリセットできます。

どの(組み合わせの)方法が効果的かは、アプリと、平均的なジョンが次の試用期間を獲得するために費やすと考える努力に依存します。不安定であるため、匿名のFirebaseと広告ID のみを使用しないようにすることをお勧めします。多要素アプローチは、最良の結果が得られるようです。どの要素を利用できるかは、アプリとその権限によって異なります。

私自身のアプリの場合、共有設定+ firstInstallTime +設定のバックアップは、最も煩わしくないが十分に効果的な方法であることがわかりました。共有設定で試用開始時刻を確認して保存した後でのみ、バックアップを要求することを確認する必要があります。共有設定の値は、firstInstallTimeよりも優先される必要があります。次に、ユーザーはアプリを再インストールして1回実行し、アプリのデータを消去して試用をリセットする必要があります。これはかなりの作業です。ただし、バックアップトランスポートのないデバイスでは、再インストールするだけでトライアルをリセットできます。

私はそのアプローチを拡張可能なライブラリとして利用できるようにしました。


1

定義により、市場にあるすべての有料Androidアプリは、購入後24時間評価できます。

24時間後に[アンインストール]に変わる[アンインストールと払い戻し]ボタンがあります。

私はこのボタンがあまりにも目立つと主張します!


17
払い戻し期間はわずか15分になりました。
複雑化

1

同じ問題を検索しているときにこの質問に遭遇しました。http: //www.timeapi.org/utc/nowのような無料の日付APIを利用できると思いますまたは他の日付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

}
}

その実用的なソリューション.....


確実に使用できますが、その場合、メインアクティビティのみが有効期限を確認できます。
RQube 2015

0

これが私のやり方です、2つのアプリを作成しました。

試用版のないものをアップロードして、有料アプリとしてストアをプレイしました。

そして、無料アプリとして試用活動をしているもの。

初回起動時の無料アプリには、トライアルとストア購入のオプションがあります。ユーザーがストア購入を選択すると、ユーザーはストアにリダイレクトされて購入しますが、ユーザーがトライアルをクリックするとトライアルアクティビティに移動します

注:@snctlnのようにオプション3を使用しましたが、変更を加えました

最初に、デバイスの時間に依存していませんでした。DBへのトライアル登録を行う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スリムテクノロジー)。

/**
     * 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ファイルをサーバーで直接ホストしたりして、人々が無料でダウンロードできるようにすることです。しかし、私はすぐにその回答またはそのソリューションへのリンクを使用してこの回答を編集します。

これが魂を救うことを願っています...いつか

ハッピーコーディング...


0

@snctlnオプション3は、phpとmysqlがインストールされているWebサーバーに、phpファイルを追加することで簡単に実行できます。

Android側から、識別子(デバイスID、Googleアカウントまたは必要なものは何でも)がHttpURLConnectionを使用してURLで引数として渡され、phpがテーブルに存在する場合、または新しい行を挿入して、最初のインストールの日付を返します。現在の日付を返します。

それは私にとってはうまくいきます。

時間があれば、コードを投稿します。

幸運を !


待ちますが、アプリの再インストール後に一意のIDが失われますか?
マクシムクニアゼフ

このIDはハードウェア、電話自体を識別します。ユーザーには表示されず、変更することもできません。彼がアプリを再インストールすると、php Webサービスは同じ電話であることを検出します。一方、ユーザーが電話を変更すると、新しい期間を楽しむことができます。
Lluis Felisart
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.