Création d'une application d'essai Android qui expire après une période de temps fixe

103

J'ai une application que je souhaite commercialiser en tant qu'application payante. Je voudrais avoir une autre version qui serait une version "d'essai" avec une limite de temps de disons, 5 jours?

Comment puis-je faire cela?

À M
la source
Google devrait vraiment prendre en charge cela dans les services Play!
poudre366
@ Powder366 en fait Google prend en charge cela, voir developer.android.com/google/play/billing/…
Yazazzello

Réponses:

186

Actuellement, la plupart des développeurs accomplissent cela en utilisant l'une des 3 techniques suivantes.

La première approche est facilement contournée, la première fois que vous exécutez l'application, enregistrez la date / l'heure dans un fichier, une base de données ou des préférences partagées et chaque fois que vous exécutez l'application après cela, vérifiez si la période d'essai est terminée. Ceci est facile à contourner car la désinstallation et la réinstallation permettront à l'utilisateur d'avoir une autre période d'essai.

La seconde approche est plus difficile à contourner, mais toujours contournable. Utilisez une bombe à retardement codée en dur. Fondamentalement, avec cette approche, vous coderez en dur une date de fin pour l'essai, et tous les utilisateurs qui téléchargent et utilisent l'application cesseront de pouvoir utiliser l'application en même temps. J'ai utilisé cette approche parce qu'elle est facile à mettre en œuvre et, pour la plupart, je n'avais tout simplement pas envie de passer par la troisième technique. Les utilisateurs peuvent contourner cela en modifiant manuellement la date sur leur téléphone, mais la plupart des utilisateurs ne se donneront pas la peine de faire une telle chose.

La troisième technique est la seule façon dont j'ai entendu parler pour vraiment être en mesure d'accomplir ce que vous voulez faire. Vous devrez configurer un serveur, puis chaque fois que votre application démarre, votre application envoie l' identifiant unique du téléphone au serveur. Si le serveur n'a pas d'entrée pour cet identifiant de téléphone, il en crée une nouvelle et note l'heure. Si le serveur dispose d'une entrée pour l'identifiant du téléphone, il effectue une simple vérification pour voir si la période d'essai a expiré. Il communique ensuite les résultats du contrôle d'expiration de l'essai à votre application. Cette approche ne doit pas être contournable, mais nécessite la mise en place d'un serveur Web et autres.

Il est toujours recommandé d'effectuer ces vérifications dans onCreate. Si l'expiration est terminée, affichez un AlertDialog avec un lien de marché vers la version complète de l'application. N'incluez qu'un bouton "OK", et une fois que l'utilisateur clique sur "OK", appelez "Terminer ()" pour mettre fin à l'activité.

snctln
la source
2
Réponse fantastique. Comme vous le dites, je pense que la deuxième option est peut-être la meilleure. C'est dommage que Google lui-même n'offre pas une sorte de système de licence car il peut encourager les développeurs de petites et grandes marques à produire encore plus d'applications Android.
Tom
8
De plus, je ne vérifierais pas au démarrage. Votre objectif est de vendre l'application, pas de punir l'utilisateur (ce n'est qu'un bonus;) Si vous l'avez configuré pour vérifier toutes les 2 minutes pendant son exécution, vous laissez l'utilisateur commencer à faire quelque chose, puis réalisez qu'il devrait payer. Si vous rendez vraiment facile le paiement et le retour au travail (je ne sais pas si vous le pouvez sous Android), je pense que vous vendrez plus que de vérifier pendant onCreate.
Whaledawg
4
@Whaledawg: Vous devez exécuter votre propre serveur car le serveur stocke l'identifiant du téléphone et l'heure de la première exécution dans une base de données qui est ensuite comparée à plus tard Aussi lorsque vous faites la vérification est purement la préférence du développeur, j'ai utilisé le hard bombe à retardement codée dans un jeu avec d'excellents résultats. L'application entière se chargerait, mais l'utilisateur ne peut interagir qu'avec la boîte de dialogue qui s'affiche, il y a un bouton dans cette boîte de dialogue qui amène l'utilisateur directement à la page d'achat du jeu. Les utilisateurs ne semblent pas déranger AFAIK puisque ce jeu figure dans le top 10 des applications payantes depuis l'ouverture de l'Android Market.
snctln
11
À quiconque hésite à utiliser l'option 3 en raison de la configuration du serveur supplémentaire, jetez un œil à Parse.com - c'est une synchronisation.
Joel Skrepnek
3
Qu'entend-on par date de fin d'essai en dur? Cela signifie-t-il que vous continuerez à publier de nouvelles versions de l'application d'essai pour toujours avec différentes dates codées en dur dans le futur?
Jasper
21

J'ai développé un SDK d'essai Android que vous pouvez simplement insérer dans votre projet Android Studio et il se chargera de toute la gestion côté serveur pour vous (y compris les périodes de grâce hors ligne).

Pour l'utiliser, simplement

Ajoutez la bibliothèque à votre module principal build.gradle

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

Initialisez la bibliothèque dans la onCreate()méthode de votre activité principale

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

Ajoutez un gestionnaire de rappel:

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

};

Pour démarrer un essai, appelez la mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback); clé de votre application et la référence SKU d'essai se trouve dans votre tableau de bord du développeur Trialy .

pseudo
la source
Il faut que les données soient activées?
Sivaram Boina
trialy n'est pas fiable
Amir Dora
1
@AmirDe Salut Amir, pourriez-vous me dire ce qui ne fonctionne pas pour vous? Je suis heureux d'aider, [email protected] Trialy fonctionne très bien pour plus de 1000 utilisateurs
Nick
@Nick ne sait pas pourquoi mon appareil exécute Android Lollipop, lorsque je règle le jour à partir du tableau de bord, puis le jour s'affiche en valeur négative après quelques minutes, cela indique que l'essai a expiré, même si j'ai encore plusieurs jours dans le tableau de bord. J'ai également testé sur un appareil nougat, semble fonctionner correctement sur naugat. peut-être qu'il a des problèmes de compatibilité avec la version Android plus ancienne
Amir Dora
1
J'utilise ce service depuis 2016, cela fonctionne bien à chaque fois. J'ai également utilisé cela dans mes projets officiels. Cela devrait être une réponse acceptée.
Tariq Mahmood
17

C'est une vieille question mais de toute façon, peut-être que cela aidera quelqu'un.

Si vous souhaitez utiliser l' approche la plus simpliste (qui échouera si l'application est désinstallée / réinstallée ou si l'utilisateur change la date de l'appareil manuellement), voici comment cela pourrait être:

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

    ...
}
Caner
la source
Eh bien en fait, si vous utilisez SharedPreferences et Android 2.2 Froyo ou supérieur, tant que vous implémentez les API de sauvegarde de données pour Google Data Synchronization, l'utilisateur ne devrait pas être en mesure d'échapper à cela sur tous les appareils ou en désinstallant, uniquement en accédant à Paramètres> Applications et effacement des données. De plus, la méthode sur Date getTimene l' est pas getTimeInMillis.
Tom
Pas d'accord, cela échouera également si l'utilisateur modifie manuellement la date de l'appareil et que se passe-t-il également si l'utilisateur efface les données manuellement?
Mohammed Azharuddin Shaikh
@Caner, il est bon de vérifier avec la préférence partagée, mais l'utilisateur effacera la mémoire du gestionnaire de configuration-> d'application et réutilisera alors que fera-t-il?
CoronaPintu
@CoronaPintu, de sorte que cette approche soit également essayée avec Firebase, cela fera la combinaison parfaite, et la période d'essai sera sur même l'application sera désinstallée.
Noor Hossain
combinez et ajoutez l'approche avec la réponse "strangetimes", cela rendra l'approche parfaite.
Noor Hossain
10

Cette question et la réponse de snctln m'ont inspiré à travailler sur une solution basée sur la méthode 3 comme mémoire de licence. Je sais que l'état actuel n'est pas destiné à une utilisation productive, mais j'aimerais savoir ce que vous en pensez! Utiliseriez-vous un tel système? Souhaitez-vous le voir comme un service cloud (vous n'avez pas de problèmes avec la configuration d'un serveur)? Préoccupé par des problèmes de sécurité ou des raisons de stabilité?

Dès que j'ai terminé la procédure de licence, je veux continuer à travailler sur le logiciel. Alors maintenant, c'est le moment où j'ai besoin de vos commentaires!

Sourcecode est hébergé sur GitHub https://github.com/MaChristmann/mobile-trial

Quelques informations sur le système: - Le système comprend trois parties, une bibliothèque Android, un serveur node.js et un configurateur pour gérer plusieurs applications d'essai et comptes d'éditeurs / développeurs.

  • Il ne prend en charge que les essais chronologiques et utilise votre compte (Play Store ou autre) plutôt qu'un identifiant de téléphone.

  • Pour la bibliothèque Android, il est basé sur la bibliothèque de vérification des licences Google Play. Je l'ai modifié pour me connecter au serveur node.js et en plus la bibliothèque essaie de reconnaître si un utilisateur a changé la date du système. Il met également en cache une licence d'évaluation récupérée dans les préférences partagées chiffrées AES. Vous pouvez configurer l'heure de validité du cache avec le configurateur. Si un utilisateur "efface les données", la bibliothèque forcera une vérification côté serveur.

  • Le serveur utilise https ainsi que la signature numérique de la réponse de vérification de licence. Il dispose également d'une API pour les applications d'essai CRUD et les utilisateurs (éditeur et développeur). Les développeurs de bibliothèques de vérification de licence peuvent tester leur implémentation de comportement dans l'application d'essai avec le résultat du test. Ainsi, dans le configurateur, vous pouvez définir explicitement la réponse de votre licence sur "sous licence", "sans licence" ou "erreur de serveur".

  • Si vous mettez à jour votre application avec une nouvelle fonctionnalité époustouflante, vous voudrez peut-être que tout le monde puisse l'essayer à nouveau. Dans le configurateur, vous pouvez renouveler la licence d'essai pour les utilisateurs avec des licences expirées en définissant un code de version qui devrait déclencher cela. Par exemple, l'utilisateur exécute votre application sur le code de version 3 et vous voulez qu'il essaie les fonctionnalités du code de version 4. S'il met à jour l'application ou la réinstalle, il peut à nouveau utiliser la période d'essai complète car le serveur sait sur quelle version il l'a essayée en dernier. temps.

  • Tout est sous licence Apache 2.0

Martin Christmann
la source
2
Vous venez de sauver ma journée, merci pour votre travail acharné. J'ai juste pensé écrire la même solution en utilisant une configuration cryptée obtenue une seule fois et en gardant la clé publique dans l'application. Donc problème majeur, je ne peux voir que comment accorder une licence? Il se peut que vous ayez toujours besoin d'un identifiant unique avec un téléphone pour éviter plusieurs subventions.
user2305886
6

Le moyen le plus simple et le meilleur pour ce faire est d'implémenter BackupSharedPreferences.

Les préférences sont conservées, même si l'application est désinstallée et réinstallée.

Enregistrez simplement la date d'installation comme préférence et vous êtes prêt à partir.

Voici la théorie: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

Voici l'exemple: La sauvegarde Android SharedPreferences ne fonctionne pas

pstorli
la source
3
L'utilisateur peut désactiver la sauvegarde dans les paramètres système.
Patrick
5

Approche 4: utilisez le temps d'installation de l'application.

Depuis le niveau d'API 9 (Android 2.3.2, 2.3.1, Android 2.3, GINGERBREAD), il y a firstInstallTime et lastUpdateTime dans PackageInfo.

Pour en savoir plus: Comment obtenir le temps d'installation de l'application depuis Android

18446744073709551615
la source
La méthode 1 de la réponse de snctln peut-elle être utilisée de manière fiable, sans être facilement contournée, ou présente-t-elle le même problème ou un problème similaire?
jwinn
J'ai testé cette méthode. Le bon côté est que cela fonctionne même si les données sont effacées. Le mauvais côté est qu'il peut être contourné par une désinstallation / réinstallation.
Jean-Philippe Jodoin
peut confirmer: sur mon Pixel, la désinstallation et la réinstallation de l'application réinitialise la première installation. cela rend assez trivial pour les utilisateurs de réinitialiser l'essai
Fabian Streitel
3

Maintenant, dans la version récente de l'abonnement d'essai gratuit Android a été ajoutée, vous ne pouvez déverrouiller toutes les fonctionnalités de votre application qu'après avoir acheté l'abonnement dans l'application pour une période d'essai gratuite. Cela permettra à l'utilisateur d'utiliser votre application pendant une période d'essai, si l'application est toujours désinstallée après la période d'essai, l'argent de l'abonnement vous sera transféré. Je n'ai pas essayé, mais juste partager une idée.

Voici la documentation

Vins
la source
2
Je souhaite que cela fonctionne pour les achats uniques. Ne fonctionne que pour les abonnements, qui peuvent être annuels ou mensuels.
jwinn
3

À mon avis, le meilleur moyen de le faire est d'utiliser simplement la base de données Firebase Realtime:

1) Ajoutez la prise en charge de Firebase à votre application

2) Sélectionnez «Authentification anonyme» pour que l'utilisateur n'ait pas à s'inscrire ou même à savoir ce que vous faites. Ceci est garanti pour être lié au compte d'utilisateur actuellement authentifié et fonctionnera donc sur tous les appareils.

3) Utilisez l'API de base de données en temps réel pour définir une valeur pour «Installed_date». Au moment du lancement, récupérez simplement cette valeur et utilisez-la.

J'ai fait la même chose et cela fonctionne très bien. J'ai pu tester cela lors de la désinstallation / réinstallation et la valeur dans la base de données en temps réel reste la même. De cette façon, votre période d'essai fonctionne sur plusieurs appareils utilisateur. Vous pouvez même mettre à jour votre install_date afin que l'application «réinitialise» la date d'essai pour chaque nouvelle version majeure.

MISE À JOUR : Après avoir testé un peu plus, il semble que Firebase anonyme semble allouer un identifiant différent au cas où vous auriez différents appareils et n'est pas garanti entre les réinstallations: / Le seul moyen garanti est d'utiliser Firebase mais de le lier à leur google Compte. Cela devrait fonctionner, mais nécessiterait une étape supplémentaire où l'utilisateur doit d'abord se connecter / s'inscrire.

Jusqu'à présent, je me suis retrouvé avec une approche légèrement moins élégante consistant simplement à vérifier les préférences sauvegardées et une date stockée dans les préférences lors de l'installation. Cela fonctionne pour les applications centrées sur les données où il est inutile pour une personne de réinstaller l'application et de saisir à nouveau toutes les données précédemment ajoutées, mais ne fonctionnerait pas pour un jeu simple.

des temps étranges
la source
J'ai la même exigence pour mon application Android et j'ai ma propre base de données / serveur Web. Les utilisateurs ne nécessitent pas de connexion, donc je prévoyais d'enregistrer l'ID de l'appareil avec la date_installée, cela fonctionnerait-il?
user636525
@strangetimes, je pense que votre solution fonctionne le mieux, pouvez-vous fournir plus d'informations? merci
DayDayHappy
Ce thread stackoverflow.com/q/41733137/1396068 suggère qu'après la réinstallation de l'application, vous obtenez un nouvel ID utilisateur, c'est-à-dire une nouvelle période d'essai
Fabian Streitel
3

Après avoir examiné toutes les options de ce fil et d'autres, voici mes conclusions

Préférences partagées, base de données Peut être effacé dans les paramètres Android, perdu après la réinstallation d'une application. Peut être sauvegardé avec le mécanisme de sauvegarde d'Android et sera restauré après une réinstallation. La sauvegarde peut ne pas toujours être disponible, mais devrait l'être sur la plupart des appareils

Stockage externe (écriture dans un fichier) Non affecté par une suppression des paramètres ou une réinstallation si nous n'écrivons pas dans le répertoire privé de l'application . Mais: vous oblige à demander à l'utilisateur son autorisation au moment de l' exécution dans les nouvelles versions d'Android, donc cela n'est probablement possible que si vous avez de toute façon besoin de cette autorisation. Peut également être sauvegardé.

PackageInfo.firstInstallTime Est réinitialisé après une réinstallation mais stable entre les mises à jour

Connectez-vous à un compte Peu importe qu'il s'agisse de son compte Google via Firebase ou de celui de votre propre serveur: la version d'évaluation est liée au compte. Créer un nouveau compte réinitialisera l'essai.

Connexion anonyme à Firebase Vous pouvez connecter un utilisateur de manière anonyme et stocker des données pour lui dans Firebase. Mais apparemment, une réinstallation de l'application et peut-être d'autres événements non documentés peuvent donner à l'utilisateur un nouvel identifiant anonyme , réinitialisant leur période d'essai. (Google lui-même ne fournit pas beaucoup de documentation à ce sujet)

ANDROID_ID Peut ne pas être disponible et peut changer dans certaines circonstances , par exemple réinitialisation d'usine. Les opinions sur l'opportunité de l'utiliser pour identifier les appareils semblent différer.

Play Advertising ID Peut être réinitialisé par l'utilisateur. Peut être désactivé par l'utilisateur en désactivant le suivi des annonces.

InstanceID Reset lors d'une réinstallation . Réinitialiser en cas d'événement de sécurité. Peut être réinitialisé par votre application.

La (combinaison de) méthodes qui fonctionne pour vous dépend de votre application et de l'effort que vous pensez que le Jean moyen mettra pour gagner une autre période d'essai. Je recommanderais d'éviter d'utiliser uniquement Firebase et Advertising ID anonymes en raison de leur instabilité. Une approche multifactorielle semble donner les meilleurs résultats. Les facteurs dont vous disposez dépendent de votre application et de ses autorisations.

Pour ma propre application, j'ai trouvé que les préférences partagées + firstInstallTime + sauvegarde des préférences étaient la méthode la moins intrusive mais aussi assez efficace. Vous devez vous assurer de ne demander une sauvegarde qu'après avoir vérifié et stocké l'heure de début de l'essai dans les préférences partagées. Les valeurs des Prefs partagés doivent avoir la priorité sur firstInstallTime. Ensuite, l'utilisateur doit réinstaller l'application, l'exécuter une fois, puis effacer les données de l'application pour réinitialiser la version d'essai, ce qui représente beaucoup de travail. Cependant, sur les appareils sans transport de sauvegarde, l'utilisateur peut réinitialiser la version d'évaluation en réinstallant simplement.

J'ai rendu cette approche disponible sous forme de bibliothèque extensible .

Fabian Streitel
la source
1

Par définition, toutes les applications Android payantes du marché peuvent être évaluées pendant 24 heures après l'achat.

Il existe un bouton «Désinstaller et rembourser» qui devient «Désinstaller» après 24 heures.

Je dirais que ce bouton est trop important!

AlexJReid
la source
17
Notez que la période de remboursement n'est plus que de 15 minutes.
Intrications
1

Je rencontre cette question en recherchant le même problème, je pense que nous pouvons utiliser des API de date gratuites comme http://www.timeapi.org/utc/now ou une autre API de date pour vérifier l'expiration de l'application de trail. cette méthode est efficace si vous souhaitez fournir la démo et que vous vous inquiétez du paiement et que vous avez besoin d'une démo fixe. :)

trouver le code ci-dessous

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

}
}

sa solution de travail .....

RQube
la source
bien sûr que vous pouvez utiliser, mais dans ce cas, seule votre activité principale pourra vérifier l'expiration.
RQube
0

Voici comment j'ai procédé au mien, j'ai créé 2 applications l'une avec une activité d'essai l'autre sans,

j'ai téléchargé celui sans activité d'essai pour jouer au magasin en tant qu'application payante,

et celui avec une activité d'essai en tant qu'application gratuite.

L'application gratuite lors du premier lancement propose des options d'essai et d'achat en magasin.Si l'utilisateur sélectionne l'achat en magasin, elle est redirigée vers le magasin pour que l'utilisateur l'achète, mais si l'utilisateur clique sur l'essai, cela l'amène à l'activité d'essai.

NB: j'ai utilisé l'option 3 comme @snctln mais avec des modifications

d'abord , je ne dépendais pas de l'heure de l'appareil, j'ai obtenu mon temps du fichier php qui fait l'enregistrement d'essai à la base de données,

deuxièmement , j'ai utilisé le numéro de série de l'appareil pour identifier de manière unique chaque appareil,

Enfin , l'application dépend de la valeur de temps renvoyée par la connexion au serveur et non de son propre temps, de sorte que le système ne peut être contourné que si le numéro de série de l'appareil est modifié, ce qui est assez stressant pour un utilisateur.

voici donc mon code (pour l'activité d'essai):

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


}

Mon fichier php ressemble à ceci (c'est une technologie 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;
        }

    }

puis sur l'activité principale, j'utilise la préférence partagée (installDate créée dans l'activité d'essai) pour surveiller le nombre de jours restants et si les jours sont terminés, je bloque l'interface utilisateur de l'activité principale avec un message qui les amène au magasin pour acheter.

Le seul inconvénient que je vois ici est que si un utilisateur Rogue achète l'application payante et décide de partager avec des applications comme Zender, le partage de fichiers ou même l'héberger le fichier apk directement sur un serveur pour que les gens le téléchargent gratuitement. Mais je suis sûr que je modifierai bientôt cette réponse avec une solution à cela ou un lien vers la solution.

J'espère que cela sauve une âme ... un jour

Codage heureux ...

Le mec mort
la source
0

L' option 3 @snctln peut être facilement effectuée en ajoutant un fichier php à un serveur Web avec php et mysql installés comme beaucoup d'entre eux.

Du côté Android, un identifiant (l'identifiant de l'appareil, le compte google ou ce que vous voulez) est passé en argument dans l'URL en utilisant HttpURLConnection et le php renvoie la date de la première installation s'il existe dans la table ou il insère une nouvelle ligne et il renvoie la date actuelle.

Ça fonctionne bien pour moi.

Si j'ai le temps, je posterai du code!

Bonne chance !

Lluis Felisart
la source
attendez mais perdez-vous votre identifiant unique après la réinstallation de l'application? !!
Maksim Kniazev
Cet identifiant identifie le matériel, le téléphone lui-même, l'utilisateur ne le voit pas et ne peut pas le changer. S'il réinstalle l'application, le service Web php détectera qu'il s'agit du même téléphone. En revanche, si l'utilisateur change de téléphone, il bénéficiera d'une nouvelle période.
Lluis Felisart