Erstellen einer Android-Testanwendung, die nach einem festgelegten Zeitraum abläuft

Ich habe eine Anwendung, die ich als kostenpflichtige App auf den Markt bringen möchte. Ich hätte gerne eine andere Version, die eine “Testversion” mit einem Zeitlimit von sagen wir 5 Tagen wäre?

Wie kann ich das tun?

Gegenwärtig erreichen die meisten Entwickler dies unter Verwendung einer der folgenden drei Techniken.

Der erste Ansatz wird leicht umgangen. Wenn Sie die App zum ersten Mal ausführen, speichern Sie das Datum / die Uhrzeit in einer Datei, database oder freigegebenen Voreinstellungen und jedes Mal, wenn Sie die App nach dieser Überprüfung ausführen, um zu sehen, ob der Testzeitraum abgelaufen ist. Dies ist leicht zu umgehen, da die Deinstallation und Neuinstallation dem Benutzer einen weiteren Testzeitraum ermöglicht.

Der zweite Ansatz ist schwieriger zu umgehen, aber immer noch umgangen. Verwenden Sie eine fest codierte Zeitbombe. Grundsätzlich werden Sie mit diesem Ansatz ein Enddatum für die Testversion festlegen und alle Benutzer, die die App herunterladen und verwenden, können die App nicht mehr gleichzeitig verwenden. Ich habe diesen Ansatz verwendet, weil er einfach zu implementieren ist und ich hatte meistens keine Lust, die Probleme der dritten Technik zu lösen. Benutzer können das umgehen, indem sie das Datum auf ihrem Handy manuell ändern, aber die meisten Benutzer werden nicht die Mühe machen, so etwas zu tun.

Die dritte Technik ist die einzige Art und Weise, von der ich gehört habe, dass ich wirklich erreichen kann, was Sie tun wollen. Sie müssen einen Server einrichten und dann, wenn Ihre Anwendung gestartet wird, sendet Ihre App die eindeutige Kennung des Telefons an den Server. Wenn der Server keinen Eintrag für diese Telefon-ID hat, erstellt er einen neuen und notiert die Uhrzeit. Wenn der Server einen Eintrag für die Telefon-ID hat, führt er eine einfache Überprüfung durch, um festzustellen, ob der Testzeitraum abgelaufen ist. Anschließend übermittelt es die Ergebnisse der Testablaufprüfung an Ihre Anwendung zurück. Dieser Ansatz sollte nicht umgangen werden können, erfordert jedoch die Einrichtung eines Webservers und so weiter.

Es ist immer eine gute Übung, diese Prüfungen im onCreate durchzuführen. Wenn das Ablaufdatum abgelaufen ist, öffnet sich ein AlertDialog mit einer Market-Verknüpfung zur Vollversion der App. Fügen Sie nur einen “OK” -Button hinzu, und sobald der Benutzer auf “OK” klickt, rufen Sie “finish ()” auf, um die Aktivität zu beenden.

Das ist eine alte Frage, aber vielleicht wird das jemandem helfen.

Falls Sie mit dem einfachsten Ansatz arbeiten möchten (der fehlschlägt, wenn die App deinstalliert / neu installiert wird oder der Benutzer das Datum des Geräts manuell ändert), könnte dies folgendermaßen aussehen:

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

Ich habe ein Android-Test-SDK entwickelt, das Sie einfach in Ihr Android Studio-Projekt einfügen können. Es kümmert sich um die gesamte serverseitige Verwaltung für Sie (einschließlich Offline-Kulanzzeiten).

Um es einfach zu benutzen

Fügen Sie die Bibliothek dem build.gradle Ihres Hauptmoduls build.gradle

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

Initialisieren Sie die Bibliothek in der onCreate() Methode Ihrer Hauptaktivität

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

Fügen Sie einen Callback-Handler hinzu:

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

Um eine Testversion zu starten, rufen Sie mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback); Ihr App-Schlüssel und die Test-SKU finden Sie in Ihrem Trialy-Entwickler-Dashboard .

Hey Leute, diese Frage und die Antwort von snctln haben mich inspiriert, an einer Lösung zu arbeiten, die auf Methode 3 als Bachelorarbeit basiert. Ich weiß, dass der aktuelle Status nicht für den produktiven Einsatz gedacht ist, aber ich würde gerne hören, was Sie darüber denken! Würden Sie ein solches System benutzen? Möchten Sie es als Cloud-Dienst sehen (ohne Probleme beim Konfigurieren eines Servers)? Aus Sicherheitsgründen oder aus Stabilitätsgründen besorgt? Sobald ich den Bachelor-process abgeschlossen habe, möchte ich weiter an der Software arbeiten. So, jetzt ist es an der Zeit, ich brauche dein Feedback!

Der Quellcode wird auf GitHub https://github.com/MaChristmann/mobile-trial gehostet

Einige Informationen zum System: – Das System besteht aus drei Teilen, einer Android-Bibliothek, einem node.js-Server und einem Konfigurator zur Verwaltung mehrerer Test-Apps und Publisher / Developer-Konten.

  • Es unterstützt nur zeitbasierte Versuche und verwendet Ihr (Spielgeschäft oder anderes) Konto anstelle einer Telefon-ID.

  • Für die Android-Bibliothek basiert sie auf der Bibliothek zur Überprüfung der Lizenzierung von Google Play. Ich habe es geändert, um eine Verbindung zum node.js-Server herzustellen, und zusätzlich versucht die Bibliothek zu erkennen, ob ein Benutzer das Systemdatum geändert hat. Es speichert auch eine abgerufene Trial-Lizenz in AES-verschlüsselten Shared Preferences. Sie können die gültige Zeit des Caches mit dem Konfigurator konfigurieren. Wenn ein Benutzer Daten löscht, erzwingt die Bibliothek eine serverseitige Überprüfung.

  • Der Server verwendet https und signiert auch die Lizenzprüfungsantwort digital. Es hat auch eine API für CRUD-Test-Apps und Benutzer (Publisher und Entwickler). Ähnlich wie bei der Lizenzverifizierung Bibliotheksentwickler können ihre Verhaltensimplementierung in der Test-App mit Testergebnissen testen. Sie können also im Konfigurator Ihre Lizenzantwort explizit auf “lizenziert”, “nicht lizenziert” oder “Servererrors” setzen.

  • Wenn Sie Ihre App mit einer neuen function aktualisieren, möchten Sie vielleicht, dass jeder es erneut versuchen kann. Im Konfigurator können Sie die Testlizenz für Benutzer mit abgelaufenen Lizenzen verlängern, indem Sie einen Versionscode festlegen, der dies auslösen soll. Zum Beispiel führt der Nutzer seine App auf Versionscode 3 und möchte, dass er functionen des Versionscodes 4 ausprobiert. Wenn er die App aktualisiert oder neu installiert, kann er die volle Testperiode erneut nutzen, da der Server weiß, welche Version er zuletzt ausprobiert hat Zeit.

  • Alles ist unter der Apache 2.0-Lizenz

Der einfachste und beste Weg, dies zu tun, ist die Implementierung BackupSharedPreferences.

Die Einstellungen bleiben erhalten, auch wenn die App deinstalliert und neu installiert wird.

Speichern Sie einfach das Installationsdatum als eine Vorliebe und Sie sind gut zu gehen.

Hier ist die Theorie: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

Hier ist das Beispiel: Android SharedPreferences Backup funktioniert nicht

Methode 4: Verwenden Sie die Installationszeit für die Anwendung.

Seit API Level 9 (Android 2.3.2, 2.3.1, Android 2.3, GINGERBREAD) gibt es in PackageInfo firstInstallTime und PackageInfo .

Um mehr zu lesen: Wie bekomme ich App-Installationszeit von Android?

Jetzt, in der letzten Version von Android-Test-Abo wurde hinzugefügt, können Sie alle functionen Ihrer App nur nach dem Kauf des Abonnements innerhalb der App für eine kostenlose Testversion entsperren. Dadurch kann der Nutzer Ihre App für einen Testzeitraum verwenden. Wenn die App nach der Testphase noch deinstalliert wird, wird das Abonnementgeld an Sie übertragen. Ich habe es nicht versucht, sondern nur eine Idee geteilt.

Hier ist Dokumentation

Meiner Meinung nach ist der beste Weg, dies zu tun, einfach die Firebase Realtime Database zu verwenden:

1) Fügen Sie Ihrer App Firebase-Unterstützung hinzu

2) Wählen Sie “Anonyme Authentifizierung”, damit sich der Benutzer nicht anmelden oder gar wissen muss, was Sie tun. Diese Verbindung wird garantiert mit dem aktuell authentifizierten Benutzerkonto verknüpft und funktioniert daher auf allen Geräten.

3) Verwenden Sie die Realtime Database API, um einen Wert für ‘installed_date’ festzulegen. Zum Zeitpunkt des Starts, einfach diesen Wert abrufen und verwenden.

Ich habe das selbe gemacht und es funktioniert super. Ich konnte dies bei der Deinstallation / Neuinstallation testen und der Wert in der Echtzeitdatenbank bleibt gleich. Auf diese Weise funktioniert Ihre Testphase über mehrere Benutzergeräte hinweg. Sie können Ihr install_date sogar versionieren, sodass die App das Testdatum für jede neue Hauptversion zurücksetzt.

UPDATE : Nach ein paar weiteren Tests scheint Firebase eine andere ID zuzuweisen, falls Sie verschiedene Geräte haben und zwischen den Neuinstallationen nicht garantiert ist: / Die einzige garantierte Möglichkeit ist die Verwendung von Firebase, aber binden Sie es an ihr Google Konto. Dies sollte funktionieren, würde jedoch einen zusätzlichen Schritt erfordern, bei dem sich der Benutzer zuerst anmelden / registrieren muss.

Ich habe bis jetzt einen etwas weniger eleganten Ansatz gefunden, einfach gegen gesicherte Präferenzen und ein in den Einstellungen gespeichertes Datum nach der Installation zu überprüfen. Dies funktioniert für datenzentrische Apps, bei denen es für eine Person sinnlos ist, die App neu zu installieren und alle zuvor hinzugefügten Daten erneut einzugeben, aber für ein einfaches Spiel nicht funktionieren würde.

Nachdem ich alle Optionen in diesem und anderen Themen angeschaut habe, sind dies meine Ergebnisse

Gemeinsame Einstellungen , database Kann in den Android-Einstellungen gelöscht werden, verloren nach einer App neu installieren. Kann mit dem Backup-Mechanismus von Android gesichert werden und wird nach einer Neuinstallation wiederhergestellt. Die Sicherung ist möglicherweise nicht immer verfügbar, sollte jedoch auf den meisten Geräten verfügbar sein

Externer Speicher (Schreiben in eine Datei) Nicht betroffen von einem Löschen der Einstellungen oder einer Neuinstallation, wenn nicht in das private Verzeichnis der Anwendung geschrieben wird . Aber: erfordert, dass Sie den Benutzer zur Laufzeit in neueren Android-Versionen um seine Erlaubnis bitten , also ist dies wahrscheinlich nur möglich, wenn Sie diese Erlaubnis sowieso benötigen. Kann auch gesichert werden.

PackageInfo.firstInstallTime Wird nach einer Neuinstallation zurückgesetzt, ist jedoch in allen Updates stabil

Bei einem Konto anmelden Es spielt keine Rolle, ob es sich um ein Google-Konto über Firebase oder eines auf Ihrem eigenen Server handelt: Die Testversion ist an das Konto gebunden. Wenn Sie ein neues Konto erstellen, wird die Testversion zurückgesetzt.

Anonyme Firebase-Anmeldung Sie können einen Benutzer anonym anmelden und Daten für ihn in Firebase speichern. Aber anscheinend kann eine Neuinstallation der App und möglicherweise andere undokumentierte Ereignisse dem Benutzer eine neue anonyme ID geben , wodurch die Testzeit zurückgesetzt wird. (Google selbst bietet hierzu nicht viel Dokumentation)

ANDROID_ID Möglicherweise nicht verfügbar und kann sich unter bestimmten Umständen ändern , z. B. Werkseinstellungen. Die Meinungen darüber, ob es eine gute Idee ist, dies zu verwenden, um Geräte zu identifizieren, scheinen sich zu unterscheiden.

Play Advertising ID Kann vom Nutzer zurückgesetzt werden. Kann vom Nutzer deaktiviert werden, indem er das Anzeigen-Tracking deaktiviert.

Instanz- ID Wird bei einer Neuinstallation zurückgesetzt . Im Falle eines Sicherheitsereignisses zurücksetzen. Kann von deiner App zurückgesetzt werden.

Welche (Kombination von) Methoden für Sie funktionieren, hängt von Ihrer App ab und davon, wie viel Aufwand der durchschnittliche John in eine weitere Testphase investiert. Ich würde empfehlen, die Verwendung von nur anonymen Firebase und Advertising ID aufgrund ihrer Instabilität zu vermeiden. Ein Multi-Faktor-Ansatz scheint die besten Ergebnisse zu liefern. Welche Faktoren für Sie verfügbar sind, hängt von Ihrer App und ihren Berechtigungen ab.

Für meine eigene App fand ich gemeinsame Einstellungen + firstInstallTime + Sicherung der Einstellungen, um die am wenigsten aufdringliche, aber auch effektiv genug Methode zu sein. Sie müssen sicherstellen, dass Sie nur ein Backup anfordern, nachdem Sie die Teststartzeit in den freigegebenen Einstellungen überprüft und gespeichert haben. Werte in den freigegebenen Voreinstellungen müssen Vorrang vor der ersten Voreinstellungszeit haben. Dann muss der Benutzer die App neu installieren, sie einmal ausführen und dann die Daten der App löschen, um die Testversion zurückzusetzen, was ziemlich viel Arbeit bedeutet. Auf Geräten ohne Backup-Transport kann der Benutzer den Test jedoch einfach durch Neuinstallation zurücksetzen.

Ich habe diesen Ansatz als erweiterbare Bibliothek verfügbar gemacht.

Per Definition können alle bezahlten Android Apps auf dem Markt 24 Stunden nach dem Kauf bewertet werden.

Es gibt eine Schaltfläche “Deinstallieren und zurückrufen”, die nach 24 Stunden in “Deinstallieren” geändert wird.

Ich würde behaupten, dass dieser Knopf viel zu auffällig ist!

Ich stolpere über diese Frage, während ich nach dem gleichen Problem suche. Ich denke, wir können freie Daten wie http://www.timeapi.org/utc/now oder ein anderes Datum verwenden, um den Ablauf der Trail-App zu überprüfen. Dieser Weg ist effizient, wenn Sie die Demo ausliefern möchten und sich um die Bezahlung sorgen und eine Demoversion benötigen. 🙂

finde den Code unten

 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 { @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("") == -1 || result.indexOf("") == -1) { System.out.println("Error $$$$$$$$$"); } else { String strTime = result.substring(result.indexOf("") + 11, result.indexOf("")); 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 } } 

seine Arbeitslösung …..

Hier ist, wie ich ging meins, ich habe 2 Apps erstellt, eine mit Testaktivität die andere ohne,

Ich habe die ohne Testaktivität hochgeladen, um den Store als kostenpflichtige App zu spielen.

und die mit Testaktivität als kostenlose App.

Die kostenlose App beim ersten Start bietet Optionen für den Test und Store-Kauf. Wenn der Nutzer einen Store-Kauf auswählt, leitet er ihn zum Store weiter, damit der Nutzer ihn kaufen kann. Wenn der Nutzer jedoch auf Trial klickt, werden die Testaktivitäten gestartet

NB: Ich habe Option 3 wie @snctln benutzt, aber mit Änderungen

Erstens , ich war nicht von der Gerätezeit abhängig, ich habe meine Zeit von der PHP-Datei, die die Testanmeldung an die db macht,

Zweitens habe ich die Geräte-Seriennummer verwendet, um jedes Gerät eindeutig zu identifizieren,

Schließlich hängt die App von dem Zeitwert ab, der von der Serververbindung zurückgegeben wird, nicht von ihrer eigenen Zeit, so dass das System nur umgangen werden kann, wenn die Seriennummer des Geräts geändert wird, was für einen Benutzer ziemlich stressig ist.

Also hier geht mein Code (für die Testaktivität):

 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() { @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 getParams() { Map params = new HashMap(); params.put(KEY_IMEI, IMEINumber); return params; } }; RequestQueue requestQueue = Volley.newRequestQueue(this); requestQueue.add(jsonObjectRequest); } } 

Meine PHP-Datei sieht so aus (es ist eine REST-Slim-Technologie):

 /** * 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('Ym-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; } } 

dann verwende ich für die Hauptaktivität die gemeinsame Präferenz (installDate, die in der Testaktivität erstellt wurde), um die Anzahl verbleibender Tage zu überwachen. Wenn die Tage vorbei sind, sperre ich die Hauptaktivitätsoberfläche mit einer Nachricht, die sie zum Laden bringt.

Die einzige negative Seite, die ich hier sehe, ist, wenn ein Rogue-Nutzer die kostenpflichtige App kauft und entscheidet, Apps mit Zender, Dateifreigabe oder sogar die apk-Datei direkt auf einem Server zu hosten, damit die Leute sie kostenlos herunterladen können. Aber ich bin sicher, dass ich diese Antwort bald mit einer Lösung oder einem Link zu der Lösung bearbeiten werde.

Hoffe das rettet eine Seele … eines Tages

Glückliche Kodierung …

@snctln Option 3 kann einfach durch Hinzufügen einer PHP-Datei zu einem Webserver mit PHP und MySQL installiert werden, wie viele von ihnen haben.

Von der Android-Seite wird eine Kennung (die Geräte-ID, Google-Konto o, was immer Sie wollen) als Argument in der URL mit HttpURLConnection übergeben und der PHP gibt das Datum der ersten Installation zurück, wenn es in der Tabelle vorhanden ist oder es eine neue Zeile einfügt es gibt das aktuelle Datum zurück.

Es funktioniert gut für mich.

Wenn ich Zeit habe, poste ich etwas Code!

Viel Glück !