Aggiunta della verifica delle licenze lato client all'app

Avviso : quando la tua app esegue la procedura di verifica delle licenze sul lato client, per i potenziali utenti malintenzionati è più facile modificare o rimuovere la logica associata a questa procedura di verifica.

Per questo motivo, ti consigliamo vivamente di eseguire la verifica delle licenze lato server.

Dopo aver configurato un account editore e un ambiente di sviluppo (consulta la sezione Configurazione per la concessione delle licenze), puoi aggiungere la verifica delle licenze alla tua app con la libreria di verifica delle licenze (LVL).

L'aggiunta della verifica delle licenze con LVL prevede le seguenti attività:

  1. Aggiunta dell'autorizzazione per le licenze al file manifest dell'applicazione.
  2. Implementazione di una norma: puoi scegliere una delle implementazioni complete fornite nella LVL o crearne una personalizzata.
  3. Implementa un Obfuscator, se il tuo Policy memorizzerà nella cache i dati delle risposte delle licenze.
  4. Aggiunta di codice per verificare la licenza nell'attività principale dell'applicazione.
  5. Implementazione di un DeviceLimiter (facoltativa e non consigliata per la maggior parte delle applicazioni).

Queste attività sono descritte nelle sezioni seguenti. Al termine dell'integrazione, dovresti essere in grado di compilare correttamente l'applicazione e iniziare i test, come descritto in Configurazione dell'ambiente di test.

Per una panoramica dell'insieme completo dei file di origine inclusi nell'LVL, consulta Riepilogo delle classi e delle interfacce LVL.

Aggiunta dell'autorizzazione per le licenze

Per poter utilizzare l'applicazione Google Play per inviare un controllo delle licenze al server, l'applicazione deve richiedere l'autorizzazione appropriata, com.android.vending.CHECK_LICENSE. Se l'applicazione non dichiara l'autorizzazione per la licenza, ma tenta di avviare un controllo delle licenze, l'LVL genera un'eccezione di sicurezza.

Per richiedere l'autorizzazione per la licenza nella tua applicazione, dichiara un elemento <uses-permission> come elemento secondario di <manifest>, come segue:

<uses-permission android:name="com.android.vending.CHECK_LICENSE" />

Ad esempio, ecco come l'applicazione di esempio LVL dichiara l'autorizzazione:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...">
    <!-- Devices >= 3 have version of Google Play that supports licensing. -->
    <uses-sdk android:minSdkVersion="3" />
    <!-- Required permission to check licensing. -->
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
    ...
</manifest>

Nota: al momento non puoi dichiarare l'autorizzazione CHECK_LICENSE nel file manifest del progetto della libreria LVL, perché SDK Tools non la unirà nei manifest delle applicazioni dipendenti. Devi invece dichiarare l'autorizzazione nel manifest di ogni applicazione dipendente.

Implementazione di un criterio

Il servizio di licenze di Google Play non determina se deve essere concesso l'accesso alla tua applicazione a un determinato utente con una determinata licenza. Piuttosto, questa responsabilità è lasciata a un'implementazione Policy da te indicata nella tua applicazione.

Policy Controller è un'interfaccia dichiarata dall'LVL progettata per contenere la logica dell'applicazione per consentire o non consentire l'accesso degli utenti, in base al risultato di un controllo della licenza. Per utilizzare l'LVL, la tua applicazione deve fornire un'implementazione di Policy.

L'interfaccia Policy dichiara due metodi, allowAccess() e processServerResponse(), che vengono richiamati da un'istanza LicenseChecker durante l'elaborazione di una risposta dal server delle licenze. Dichiara inoltre un'enum chiamata LicenseResponse, che specifica il valore di risposta della licenza passato nelle chiamate a processServerResponse().

  • processServerResponse() consente di pre-elaborare i dati di risposta non elaborati ricevuti dal server di licenze, prima di determinare se concedere l'accesso.

    Un'implementazione tipica estrae alcuni o tutti i campi dalla risposta alla licenza e li archivia localmente in un archivio permanente, ad esempio tramite l'archiviazione SharedPreferences, per garantire che i dati siano accessibili durante le chiamate delle applicazioni e i cicli di spegnimento del dispositivo. Ad esempio, un elemento Policy conserverebbe il timestamp dell'ultimo controllo riuscito, il conteggio dei nuovi tentativi, il periodo di validità della licenza e informazioni simili in un archivio permanente, anziché reimpostare i valori a ogni avvio dell'applicazione.

    Quando archivi i dati delle risposte in locale, Policy deve garantire che i dati siano offuscati (consulta la sezione Implementazione di un offuscamento di seguito).

  • allowAccess() determina se concedere all'utente l'accesso alla tua applicazione in base ai dati di risposta delle licenze disponibili (dal server di licenze o dalla cache) o altre informazioni specifiche dell'applicazione. Ad esempio, l'implementazione di allowAccess() potrebbe prendere in considerazione criteri aggiuntivi, come l'utilizzo o altri dati recuperati da un server di backend. In tutti i casi, un'implementazione di allowAccess() deve restituire true solo se l'utente dispone della licenza per utilizzare l'applicazione, come stabilito dal server di licenze, o se si verifica un problema temporaneo di rete o di sistema che impedisce il completamento del controllo della licenza. In questi casi, la tua implementazione può mantenere un numero di risposte ai nuovi tentativi e consentire temporaneamente l'accesso fino al completamento del controllo della licenza successivo.

Per semplificare il processo di aggiunta delle licenze all'applicazione e per fornire un'illustrazione di come deve essere progettato un Policy, l'LVL include due implementazioni Policy complete che puoi utilizzare senza modifiche o adattarsi alle tue esigenze:

  • ServerManagedPolicy, un Policy flessibile che utilizza impostazioni fornite dal server e risposte memorizzate nella cache per gestire l'accesso a varie condizioni di rete.
  • StrictPolicy, che non memorizza nella cache i dati delle risposte e consente l'accesso solo se il server restituisce una risposta concessa in licenza.

Per la maggior parte delle applicazioni, si consiglia di utilizzare ServerManagedPolicy. ServerManagedPolicy è l'impostazione predefinita LVL ed è integrata con l'applicazione di esempio LVL.

Linee guida per le norme personalizzate

Nell'implementazione delle licenze, puoi utilizzare uno dei criteri completi forniti in LVL (ServerManagedPolicy o StrictPolicy) oppure creare un criterio personalizzato. Per qualsiasi tipo di criterio personalizzato, esistono diversi importanti punti di progettazione da comprendere e tenere in considerazione nell'implementazione.

Il server di licenze applica limiti generali per le richieste per evitare un uso eccessivo delle risorse che potrebbe causare il denial of service. Quando un'applicazione supera il limite di richiesta, il server di licenze restituisce una risposta 503, che viene trasmessa alla tua applicazione come errore generale del server. Ciò significa che nessuna risposta relativa alla licenza sarà disponibile per l'utente finché il limite non viene reimpostato, il che può interessare l'utente per un periodo indefinito.

Se stai progettando una norma personalizzata, ti consigliamo di Policy:

  1. Memorizza nella cache (e offusca correttamente) la risposta più recente relativa alla licenza nell'archiviazione permanente locale.
  2. Restituisce la risposta memorizzata nella cache per tutti i controlli delle licenze, finché la risposta memorizzata nella cache è valida, anziché inviare una richiesta al server di licenze. Ti consigliamo vivamente di impostare la validità della risposta in base al valore aggiuntivo VT fornito dal server. Per ulteriori informazioni, vedi Informazioni aggiuntive per la risposta del server.
  3. Utilizza un periodo di backoff esponenziale. Se un nuovo tentativo genera errori. Tieni presente che il client Google Play fa automaticamente dei tentativi per le richieste non riuscite, pertanto nella maggior parte dei casi non è necessario che il tuo Policy le riprovi.
  4. Fornisce un "periodo di tolleranza" che consente all'utente di accedere alla tua applicazione per un periodo di tempo limitato o di un numero di utilizzi limitato, mentre è in corso un nuovo tentativo di controllo della licenza. Il periodo di tolleranza è vantaggioso per l'utente, in quanto consente l'accesso fino al completamento del controllo della licenza successivo e ti offre un vantaggio per l'applicazione di un limite rigido all'accesso alla tua applicazione quando non è disponibile una risposta alla licenza valida.

Progettare la tua Policy in base alle linee guida elencate sopra è fondamentale, perché garantisce la migliore esperienza possibile agli utenti e al contempo offre un controllo efficace sulla tua applicazione anche in condizioni di errore.

Tieni presente che qualsiasi Policy può utilizzare le impostazioni fornite dal server di licenze per gestire la validità e la memorizzazione nella cache, riprovare il periodo di tolleranza e altro ancora. L'estrazione delle impostazioni fornite dal server è semplice e il relativo utilizzo è vivamente consigliato. Vedi l'implementazione di ServerManagedPolicy per un esempio di come estrarre e utilizzare gli extra. Per un elenco delle impostazioni del server e informazioni su come utilizzarle, consulta la sezione Elementi aggiuntivi per la risposta del server.

Criterio gestito da server

L'LVL include un'implementazione completa e consigliata dell'interfaccia Policy chiamata ServerManagedPolicy. L'implementazione è integrata con le classi LVL e funge da Policy predefinito nella libreria.

ServerManagedPolicy gestisce tutte le risposte per le licenze e i nuovi tentativi. Memorizza nella cache tutti i dati di risposta localmente in un file SharedPreferences, offuscandolo con l'implementazione Obfuscator dell'applicazione. Ciò garantisce che i dati relativi alle risposte delle licenze siano protetti e persistenti durante i cicli di spegnimento e riaccensione del dispositivo. ServerManagedPolicy fornisce implementazioni concrete dei metodi di interfaccia processServerResponse() e allowAccess() e include anche una serie di metodi e tipi di supporto per la gestione delle risposte alle licenze.

È importante sottolineare che una caratteristica chiave di ServerManagedPolicy è l'utilizzo di impostazioni fornite dal server come base per la gestione delle licenze per tutto il periodo di rimborso di un'applicazione e attraverso condizioni di rete ed errore variabili. Quando un'applicazione contatta il server di Google Play per il controllo delle licenze, il server aggiunge diverse impostazioni come coppie chiave-valore nel campo Extra di determinati tipi di risposta delle licenze. Ad esempio, il server fornisce valori consigliati per, ad esempio, il periodo di validità della licenza dell'applicazione, il periodo di tolleranza per i nuovi tentativi e il numero massimo di nuovi tentativi consentiti. ServerManagedPolicy estrae i valori dalla risposta di licenza nel metodo processServerResponse() e li controlla nel metodo allowAccess(). Per un elenco delle impostazioni fornite dal server utilizzate da ServerManagedPolicy, vedi Extra per la risposta del server.

Per praticità, prestazioni ottimali e per il vantaggio dell'utilizzo delle impostazioni delle licenze del server Google Play, ti consigliamo vivamente di utilizzare ServerManagedPolicy come licenza Policy..

Se ti preoccupa la sicurezza dei dati relativi alle risposte delle licenze archiviati localmente in SharedPreferences, puoi utilizzare un algoritmo di offuscamento più efficace o progettare un Policy più rigido che non archivia i dati delle licenze. LVL include un esempio di Policy. Per ulteriori informazioni, consulta StrictPolicy.

Per utilizzare ServerManagedPolicy, è sufficiente importarlo nell'attività, creare un'istanza e passare un riferimento all'istanza durante la creazione di LicenseChecker. Per ulteriori informazioni, consulta Istantanei LicenseChecker e LicenseCheckerCallback.

Criterio rigoroso

L'LVL include un'implementazione completa alternativa dell'interfaccia Policy chiamata StrictPolicy. L'implementazione di StrictPolicy fornisce un criterio più restrittivo rispetto a ServerManagedPolicy, in quanto non consente all'utente di accedere all'applicazione a meno che al momento dell'accesso non venga ricevuta una risposta relativa alla licenza dal server che indichi che l'utente dispone della licenza.

La caratteristica principale di StrictPolicy è che non archivia nessun dato della risposta della licenza localmente in un archivio permanente. Poiché non sono archiviati dati, le richieste di nuovo tentativo non vengono monitorate e le risposte memorizzate nella cache non possono essere utilizzate per completare i controlli delle licenze. Policy consente l'accesso solo se:

  • La risposta relativa alla licenza viene ricevuta dal server di licenze.
  • La risposta relativa alle licenze indica che l'utente dispone della licenza per accedere all'applicazione.

L'utilizzo di StrictPolicy è appropriato se la tua priorità principale è garantire che, in tutti i casi possibili, nessun utente sia autorizzato ad accedere all'applicazione a meno che non venga confermata la licenza dell'utente al momento dell'utilizzo. Inoltre, il criterio offre un po' più di sicurezza rispetto a ServerManagedPolicy: poiché non esistono dati memorizzati nella cache localmente, è impossibile che un utente malintenzionato possa manomettere i dati memorizzati nella cache e ottenere l'accesso all'applicazione.

Allo stesso tempo, questo Policy presenta una sfida per gli utenti normali, poiché significa che non saranno in grado di accedere all'applicazione quando non è disponibile una connessione di rete (cellulare o Wi-Fi). Un altro effetto collaterale è che l'applicazione invierà più richieste di controllo delle licenze al server, dato che non è possibile utilizzare una risposta memorizzata nella cache.

Nel complesso, questo criterio rappresenta un compromesso tra un certo grado di comodità per gli utenti e la sicurezza assoluta e il controllo dell'accesso. Valuta con attenzione il compromesso prima di utilizzare questo Policy.

Per utilizzare StrictPolicy, devi semplicemente importarlo nella tua attività, creare un'istanza e passare un riferimento alla stessa durante la creazione di LicenseChecker. Per ulteriori informazioni, consulta Instantiate LicenseChecker e LicenseCheckerCallback per ulteriori informazioni.

Una tipica implementazione Policy deve salvare i dati di risposta della licenza per un'applicazione in un archivio permanente, in modo che siano accessibili durante tutte le chiamate delle applicazioni e i cicli di spegnimento del dispositivo. Ad esempio, Policy manterrà il timestamp dell'ultimo controllo riuscito, il conteggio dei nuovi tentativi, il periodo di validità della licenza e informazioni simili in un archivio permanente, invece di reimpostare i valori a ogni avvio dell'applicazione. Il valore predefinito Policy incluso in LVL, ServerManagedPolicy archivia i dati relativi alle risposte delle licenze in un'istanza SharedPreferences, per garantire che i dati siano permanenti.

Poiché Policy userà i dati archiviati delle risposte delle licenze per determinare se consentire o meno l'accesso all'applicazione, deve garantire che tutti i dati archiviati siano protetti e non possano essere riutilizzati o manipolati da un utente root su un dispositivo. Nello specifico, Policy deve sempre offuscare i dati prima di archiviarli, utilizzando una chiave univoca per l'applicazione e il dispositivo. L'offuscamento mediante una chiave specifica per l'applicazione e per il dispositivo è fondamentale, in quanto impedisce la condivisione dei dati offuscati tra applicazioni e dispositivi.

L'LVL aiuta l'applicazione ad archiviare i dati di risposta delle licenze in modo sicuro e persistente. Innanzitutto, fornisce un'interfaccia Obfuscator che consente alla tua applicazione di fornire l'algoritmo di offuscamento di sua scelta per i dati archiviati. Basandosi su questo, l'LVL fornisce la classe helper PreferenceObfuscator, che gestisce la maggior parte del lavoro delle chiamate alla classe Obfuscator dell'applicazione e della lettura e della scrittura dei dati offuscati in un'istanza SharedPreferences.

L'LVL fornisce un'implementazione Obfuscator completa denominata AESObfuscator che utilizza la crittografia AES per offuscare i dati. Puoi utilizzare AESObfuscator nella tua applicazione senza modifiche oppure puoi adattarlo alle tue esigenze. Se utilizzi un Policy (ad esempio ServerManagedPolicy) che memorizza nella cache i dati di risposta delle licenze, ti consigliamo di utilizzare AESObfuscator come base per l'implementazione di Obfuscator. Per ulteriori informazioni, consulta la sezione successiva.

Offuscatore AES

LVL include un'implementazione completa e consigliata dell'interfaccia Obfuscator chiamata AESObfuscator. L'implementazione è integrata con l'applicazione LVL di esempio e funge da Obfuscator predefinito nella libreria.

AESObfuscator offre un offuscamento sicuro dei dati utilizzando l'algoritmo AES per criptare e decriptare i dati nel momento in cui vengono scritti o letti dallo spazio di archiviazione. L'elemento Obfuscator avvia la crittografia utilizzando tre campi di dati forniti dall'applicazione:

  1. Un sale: un array di byte casuali da utilizzare per ogni offuscamento.
  2. Una stringa di identificatore dell'applicazione, in genere il nome del pacchetto dell'applicazione.
  3. Una stringa di identificatore del dispositivo, ricavata dal maggior numero possibile di origini specifiche del dispositivo, in modo da renderlo il più univoco.

Per utilizzare AESObfuscator, devi prima importarlo nella tua Attività. Dichiara un array finale statico privato che contiene i byte di sale e inizializzalo su 20 byte generati in modo casuale.

Kotlin

// Generate 20 random bytes, and put them here.
private val SALT = byteArrayOf(
        -46, 65, 30, -128, -103, -57, 74, -64, 51, 88,
        -95, -45, 77, -117, -36, -113, -11, 32, -64, 89
)

Java

...
    // Generate 20 random bytes, and put them here.
    private static final byte[] SALT = new byte[] {
     -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95,
     -45, 77, -117, -36, -113, -11, 32, -64, 89
     };
    ...

Quindi, dichiara una variabile che contiene un identificatore del dispositivo e genera un valore corrispondente in qualsiasi modo necessario. Ad esempio, l'applicazione di esempio inclusa nell'LVL esegue una query sulle impostazioni di sistema per android.Settings.Secure.ANDROID_ID, che è univoca per ogni dispositivo.

Tieni presente che, a seconda delle API che utilizzi, la tua applicazione potrebbe dover richiedere autorizzazioni aggiuntive per acquisire informazioni specifiche del dispositivo. Ad esempio, per eseguire query sull'TelephonyManager al fine di ottenere il codice IMEI o i dati correlati del dispositivo, l'applicazione dovrà richiedere anche l'autorizzazione android.permission.READ_PHONE_STATE nel file manifest.

Prima di richiedere nuove autorizzazioni all'unico scopo di acquisire informazioni specifiche del dispositivo da utilizzare nel tuo Obfuscator, valuta in che modo ciò potrebbe influire sulla tua applicazione o sui suoi filtri su Google Play (dal momento che alcune autorizzazioni possono causare l'aggiunta del <uses-feature> associato agli strumenti di creazione dell'SDK).

Infine, crea un'istanza di AESObfuscator passando il sale, l'identificatore dell'applicazione e l'identificatore del dispositivo. Puoi costruire direttamente l'istanza, durante la creazione di Policy e LicenseChecker. Ecco alcuni esempi:

Kotlin

    ...
    // Construct the LicenseChecker with a Policy.
    private val checker = LicenseChecker(
            this,
            ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
            BASE64_PUBLIC_KEY
    )
    ...

Java

    ...
    // Construct the LicenseChecker with a Policy.
    checker = new LicenseChecker(
        this, new ServerManagedPolicy(this,
            new AESObfuscator(SALT, getPackageName(), deviceId)),
        BASE64_PUBLIC_KEY // Your public licensing key.
        );
    ...

Per un esempio completo, vedi MainActivity nell'applicazione di esempio LVL.

Controllo della licenza da un'attività

Dopo aver implementato Policy per la gestione dell'accesso alla tua applicazione, il passaggio successivo prevede l'aggiunta di un controllo delle licenze all'applicazione, che avvia una query al server di licenze, se necessario, e gestisce l'accesso all'applicazione in base alla risposta della licenza. Tutto il lavoro di aggiunta del controllo della licenza e gestione della risposta si svolge nel file di origine Activity principale.

Per aggiungere il controllo della licenza e gestire la risposta, devi:

  1. Aggiungere importazioni
  2. Implementa LicenseCheckerCallback come classe interna privata
  3. Crea un gestore per pubblicare post da LicenseCheckerCallback nel thread dell'interfaccia utente
  4. Instantiate LicenseChecker e LicenseCheckerCallback
  5. Chiama checkAccess() per avviare il controllo delle licenze
  6. Incorpora la chiave pubblica per le licenze
  7. Chiama il metodo onDestroy() di LicenseChecker per chiudere le connessioni IPC.

Queste attività sono descritte nelle sezioni seguenti.

Panoramica del controllo e della risposta delle licenze

Nella maggior parte dei casi, devi aggiungere il controllo delle licenze all'elemento Activity principale dell'applicazione, nel metodo onCreate(). Ciò garantisce che, quando l'utente avvia direttamente l'applicazione, il controllo della licenza venga richiamato immediatamente. In alcuni casi, puoi aggiungere controlli delle licenze anche in altre località. Ad esempio, se l'applicazione include più componenti delle attività che altre applicazioni possono avviare entro il giorno Intent, puoi aggiungere controlli delle licenze in tali attività.

Il controllo delle licenze consiste in due azioni principali:

  • Una chiamata a un metodo per avviare il controllo della licenza. Nel LVL, si tratta di una chiamata al metodo checkAccess() di un oggetto LicenseChecker che crei.
  • Un callback che restituisce il risultato del controllo della licenza. Nell'LVL, si tratta di un'interfaccia LicenseCheckerCallback che implementi. L'interfaccia dichiara due metodi, allow() e dontAllow(), richiamati dalla libreria in base al risultato del controllo delle licenze. Puoi implementare questi due metodi con la logica necessaria per consentire o impedire all'utente di accedere alla tua applicazione. Tieni presente che questi metodi non determinano se consentire l'accesso. La determinazione è responsabilità dell'implementazione di Policy. Piuttosto, questi metodi si limitano a fornire i comportamenti dell'applicazione su come consentire e non consentire l'accesso (e gestire gli errori dell'applicazione).

    I metodi allow() e dontAllow() forniscono un "motivo" per la risposta, che può essere uno dei valori Policy, LICENSED, NOT_LICENSED o RETRY. In particolare, devi gestire il caso in cui il metodo riceve la risposta RETRY per dontAllow() e fornire all'utente un pulsante "Riprova", che potrebbe essere stato causato dal fatto che il servizio non era disponibile durante la richiesta.

Figura 1. Panoramica di una tipica interazione di controllo delle licenze.

Il diagramma riportato sopra illustra come avviene un tipico controllo delle licenze:

  1. Il codice nell'attività principale dell'applicazione crea istanze di oggetti LicenseCheckerCallback e LicenseChecker. Durante la creazione di LicenseChecker, il codice trasmette Context, un'implementazione Policy da utilizzare e la chiave pubblica dell'account editore per la concessione in licenza come parametri.
  2. Il codice chiama quindi il metodo checkAccess() nell'oggetto LicenseChecker. L'implementazione del metodo chiama Policy per determinare se è presente una risposta per la licenza valida memorizzata nella cache localmente in SharedPreferences.
    • In tal caso, l'implementazione checkAccess() chiama allow().
    • In caso contrario, LicenseChecker avvia una richiesta di controllo della licenza che viene inviata al server di licenze.

    Nota: il server di licenze restituisce sempre LICENSED quando esegui il controllo della licenza di una bozza di applicazione.

  3. Quando viene ricevuta una risposta, LicenseChecker crea uno strumento di convalida delle licenze che verifica i dati della licenza firmata ed estrae i campi della risposta, quindi li passa al tuo Policy per un'ulteriore valutazione.
    • Se la licenza è valida, Policy memorizza nella cache la risposta in SharedPreferences e invia una notifica allo strumento di convalida, che a sua volta chiama il metodo allow() sull'oggetto LicenseCheckerCallback.
    • Se la licenza non è valida, Policy invia una notifica allo strumento di convalida, che chiama il metodo dontAllow() su LicenseCheckerCallback.
  4. In caso di errore locale o del server recuperabile, ad esempio quando la rete non è disponibile per inviare la richiesta, LicenseChecker passa una risposta RETRY al metodo processServerResponse() dell'oggetto Policy.

    Inoltre, entrambi i metodi di callback allow() e dontAllow() ricevono un argomento reason. In genere il motivo del metodo allow() è Policy.LICENSED o Policy.RETRY, mentre il motivo dontAllow() è in genere Policy.NOT_LICENSED o Policy.RETRY. Questi valori di risposta sono utili per mostrare una risposta appropriata per l'utente, ad esempio fornendo un pulsante "Riprova" quando dontAllow() risponde con Policy.RETRY, il che potrebbe essere stato perché il servizio non era disponibile.

  5. In caso di errore dell'applicazione, ad esempio quando l'applicazione tenta di controllare la licenza di un nome di pacchetto non valido, LicenseChecker passa una risposta di errore al metodo applicationError() di LicenseCheckerCallback.

Tieni presente che, oltre ad avviare il controllo della licenza e gestire il risultato, descritti nelle sezioni seguenti, la tua applicazione deve anche fornire un'implementazione dei criteri e, se Policy archivia i dati di risposta (come ServerManagedPolicy), un'implementazione Obfuscator.

Aggiungi importazioni

Per prima cosa, apri il file del corso dell'attività principale dell'applicazione e importa LicenseChecker e LicenseCheckerCallback dal pacchetto LVL.

Kotlin

import com.google.android.vending.licensing.LicenseChecker
import com.google.android.vending.licensing.LicenseCheckerCallback

Java

import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;

Se utilizzi l'implementazione predefinita di Policy fornita con LVL, ServerManagedPolicy, importala anche insieme all'oggetto AESObfuscator. Se utilizzi Policy o Obfuscator personalizzati, importali.

Kotlin

import com.google.android.vending.licensing.ServerManagedPolicy
import com.google.android.vending.licensing.AESObfuscator

Java

import com.google.android.vending.licensing.ServerManagedPolicy;
import com.google.android.vending.licensing.AESObfuscator;

Implementare LicenseCheckerCallback come classe interna privata

LicenseCheckerCallback è un'interfaccia fornita dall'LVL per la gestione dei risultati di un controllo delle licenze. Per supportare le licenze tramite LVL, devi implementare LicenseCheckerCallback e i relativi metodi per consentire o non consentire l'accesso all'applicazione.

Il risultato di un controllo della licenza è sempre una chiamata a uno dei metodi LicenseCheckerCallback, effettuata in base alla convalida del payload della risposta, al codice di risposta del server stesso e a qualsiasi elaborazione aggiuntiva fornita dal tuo Policy. La tua applicazione può implementare i metodi in qualsiasi modo necessario. In generale, è preferibile utilizzare metodi semplici, limitandoli alla gestione dello stato dell'interfaccia utente e dell'accesso alle applicazioni. Se vuoi aggiungere ulteriori elaborazioni delle risposte alle licenze, ad esempio contattando un server di backend o applicando vincoli personalizzati, ti consigliamo di incorporare il codice in Policy, anziché inserirlo nei metodi LicenseCheckerCallback.

Nella maggior parte dei casi, devi dichiarare la tua implementazione di LicenseCheckerCallback come classe privata all'interno della classe Activity principale dell'applicazione.

Implementa i metodi allow() e dontAllow() in base alle esigenze. Per iniziare, puoi utilizzare nei metodi semplici comportamenti di gestione dei risultati, come la visualizzazione del risultato della licenza in una finestra di dialogo. Ciò ti consente di eseguire l'applicazione più rapidamente e può essere utile per il debug. In seguito, dopo aver determinato i comportamenti esatti che vuoi, puoi aggiungere una gestione più complessa.

Ecco alcuni suggerimenti per la gestione delle risposte senza licenza in dontAllow():

  • Mostra all'utente una finestra di dialogo "Riprova" con un pulsante per avviare una nuova verifica delle licenze se il reason fornito è Policy.RETRY.
  • Visualizza una finestra di dialogo "Acquista questa applicazione" contenente un pulsante che rimanda direttamente all'utente alla pagina dei dettagli dell'applicazione su Google Play, da cui l'utente può acquistare l'applicazione. Per ulteriori informazioni su come configurare questi link, consulta la sezione Collegamento ai tuoi prodotti.
  • Visualizza un avviso popup che indichi che le funzionalità dell'applicazione sono limitate perché non è concessa in licenza.

L'esempio seguente mostra come l'applicazione di esempio LVL implementa LicenseCheckerCallback e mostra una finestra di dialogo che mostra i metodi che mostrano il controllo delle licenze.

Kotlin

private inner class MyLicenseCheckerCallback : LicenseCheckerCallback {

    override fun allow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        // Should allow user access.
        displayResult(getString(R.string.allow))
    }

    override fun dontAllow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        displayResult(getString(R.string.dont_allow))

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY)
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET)
        }
    }
}

Java

private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
    public void allow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        // Should allow user access.
        displayResult(getString(R.string.allow));
    }

    public void dontAllow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        displayResult(getString(R.string.dont_allow));

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY);
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET);
        }
    }
}

Inoltre, devi implementare il metodo applicationError(), che il LVL chiama per consentire alla tua applicazione di gestire errori non ripetibili. Per un elenco di questi errori, consulta la sezione Codici di risposta del server nella documentazione di riferimento sulle licenze. Puoi implementare il metodo in qualsiasi modo necessario. Nella maggior parte dei casi, il metodo deve registrare il codice di errore e chiamare dontAllow().

Crei un gestore per la pubblicazione da LicenseCheckerCallback nel thread dell'interfaccia utente

Durante il controllo della licenza, l'LVL passa la richiesta all'applicazione Google Play, che gestisce la comunicazione con il server di licenze. L'LVL passa la richiesta tramite IPC asincrono (utilizzando Binder), in modo che l'elaborazione effettiva e la comunicazione di rete non avvengano su un thread gestito dalla tua applicazione. Analogamente, quando l'applicazione Google Play riceve il risultato, richiama un metodo di callback su IPC, che a sua volta viene eseguito in un pool di thread IPC nel processo dell'applicazione.

La classe LicenseChecker gestisce la comunicazione IPC dell'applicazione con l'applicazione Google Play, inclusa la chiamata che invia la richiesta e il callback che riceve la risposta. LicenseChecker monitora anche le richieste di licenza aperte e gestisce i relativi timeout.

Per poter gestire correttamente i timeout ed elaborare le risposte in arrivo senza influire sul thread dell'interfaccia utente dell'applicazione, LicenseChecker crea un thread in background al momento della creazione di un'istanza. Nel thread, esegue tutte le elaborazioni dei risultati del controllo delle licenze, indipendentemente dal fatto che il risultato sia una risposta ricevuta dal server o un errore di timeout. Al termine dell'elaborazione, l'LVL chiama i metodi LicenseCheckerCallback dal thread in background.

Per la tua applicazione, ciò significa che:

  1. In molti casi, i metodi LicenseCheckerCallback verranno richiamati da un thread in background.
  2. Questi metodi non saranno in grado di aggiornare lo stato o richiamare alcuna elaborazione nel thread dell'interfaccia utente, a meno che non crei un gestore nel thread dell'interfaccia utente e i tuoi metodi di callback non vengano pubblicati nel gestore.

Se vuoi che i tuoi metodi LicenseCheckerCallback aggiornino il thread dell'interfaccia utente, crea un valore Handler nel metodo onCreate() dell'attività principale, come mostrato di seguito. In questo esempio, i metodi LicenseCheckerCallback dell'applicazione di esempio LVL (vedi sopra) chiamano displayResult() per aggiornare il thread dell'interfaccia utente tramite il metodo post() del gestore.

Kotlin

    private lateinit var handler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        handler = Handler()
    }

Java

    private Handler handler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        handler = new Handler();
    }

Quindi, nei tuoi metodi LicenseCheckerCallback, puoi utilizzare i metodi del gestore per pubblicare oggetti Runnable o Message nel Gestore. Ecco come l'applicazione di esempio inclusa nel file LVL pubblica un elemento eseguibile in un gestore nel thread dell'interfaccia utente per visualizzare lo stato della licenza.

Kotlin

private fun displayResult(result: String) {
    handler.post {
        statusText.text = result
        setProgressBarIndeterminateVisibility(false)
        checkLicenseButton.isEnabled = true
    }
}

Java

private void displayResult(final String result) {
        handler.post(new Runnable() {
            public void run() {
                statusText.setText(result);
                setProgressBarIndeterminateVisibility(false);
                checkLicenseButton.setEnabled(true);
            }
        });
    }

Crea un'istanza LicenseChecker e LicenseCheckerCallback

Nel metodo onCreate() dell'attività principale, crea istanze private di LicenseCheckerCallback e LicenseChecker. Devi prima istruire LicenseCheckerCallback, perché devi passare un riferimento a quell'istanza quando chiami il costruttore per LicenseChecker.

Quando crei un'istanza di LicenseChecker, devi passare questi parametri:

  • L'applicazione Context
  • Un riferimento all'implementazione Policy da utilizzare per il controllo delle licenze. Nella maggior parte dei casi, utilizzerai l'implementazione predefinita di Policy fornita da LVL, ServerManagedPolicy.
  • La variabile Stringa che contiene la chiave pubblica del tuo account editore per le licenze.

Se utilizzi ServerManagedPolicy, non dovrai accedere direttamente alla classe, ma puoi creare un'istanza della classe nel costruttore LicenseChecker, come mostrato nell'esempio di seguito. Tieni presente che devi passare un riferimento a una nuova istanza di Obfuscator quando crei ServerManagedPolicy.

L'esempio seguente mostra la creazione di istanze di LicenseChecker e LicenseCheckerCallback dal metodo onCreate() di una classe Activity.

Kotlin

class MainActivity : AppCompatActivity() {
    ...
    private lateinit var licenseCheckerCallback: LicenseCheckerCallback
    private lateinit var checker: LicenseChecker

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = MyLicenseCheckerCallback()

        // Construct the LicenseChecker with a Policy.
        checker = LicenseChecker(
                this,
                ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
                BASE64_PUBLIC_KEY // Your public licensing key.
        )
        ...
    }
}

Java

public class MainActivity extends Activity {
    ...
    private LicenseCheckerCallback licenseCheckerCallback;
    private LicenseChecker checker;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = new MyLicenseCheckerCallback();

        // Construct the LicenseChecker with a Policy.
        checker = new LicenseChecker(
            this, new ServerManagedPolicy(this,
                new AESObfuscator(SALT, getPackageName(), deviceId)),
            BASE64_PUBLIC_KEY // Your public licensing key.
            );
        ...
    }
}

Tieni presente che LicenseChecker chiama i metodi LicenseCheckerCallback dal thread della UI solo se esiste una risposta della licenza valida memorizzata nella cache localmente. Se il controllo della licenza viene inviato al server, i callback provengono sempre dal thread in background, anche per gli errori di rete.

Chiama checkAccess() per avviare il controllo delle licenze

Nella tua Attività principale, aggiungi una chiamata al metodo checkAccess() dell'istanza LicenseChecker. Nella chiamata, passa un riferimento all'istanza LicenseCheckerCallback come parametro. Se devi gestire eventuali effetti speciali dell'interfaccia utente o la gestione dello stato prima della chiamata, potresti trovare utile chiamare checkAccess() da un metodo wrapper. Ad esempio, l'applicazione LVL di esempio chiama checkAccess() da un metodo wrapper doCheck():

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Call a wrapper method that initiates the license check
        doCheck()
        ...
    }
    ...
    private fun doCheck() {
        checkLicenseButton.isEnabled = false
        setProgressBarIndeterminateVisibility(true)
        statusText.setText(R.string.checking_license)
        checker.checkAccess(licenseCheckerCallback)
    }

Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Call a wrapper method that initiates the license check
        doCheck();
        ...
    }
    ...
    private void doCheck() {
        checkLicenseButton.setEnabled(false);
        setProgressBarIndeterminateVisibility(true);
        statusText.setText(R.string.checking_license);
        checker.checkAccess(licenseCheckerCallback);
    }

Incorpora la chiave pubblica per le licenze

Per ogni applicazione, il servizio Google Play genera automaticamente una coppia di chiavi pubblica/privata RSA a 2048 bit utilizzata per le licenze e la fatturazione in-app. La coppia di chiavi è associata all'applicazione in modo univoco. Sebbene sia associata all'applicazione, la coppia di chiavi non è la chiave che utilizzi per firmare le applicazioni (o da questa derivata).

Google Play Console espone la chiave pubblica per le licenze agli sviluppatori che hanno eseguito l'accesso alla Play Console, ma mantiene la chiave privata nascosta a tutti gli utenti in un luogo sicuro. Quando un'applicazione richiede il controllo della licenza per un'applicazione pubblicata nel tuo account, il server di licenze firma la risposta relativa alla licenza utilizzando la chiave privata della coppia di chiavi dell'applicazione. Quando l'LVL riceve la risposta, utilizza la chiave pubblica fornita dall'applicazione per verificare la firma della risposta relativa alla licenza.

Per aggiungere licenze a un'applicazione, devi ottenere la chiave pubblica per la licenza dell'applicazione e copiarla nell'applicazione. Per trovare la chiave pubblica della tua applicazione per la licenza:

  1. Vai a Google Play Console e accedi. Assicurati di accedere all'account da cui è stata pubblicata (o verrà pubblicata) l'applicazione che hai concesso in licenza.
  2. Nella pagina dei dettagli dell'applicazione, individua il link Servizi e API e fai clic.
  3. Nella pagina Servizi e API, individua la sezione Licenze e fatturazione in-app. La chiave pubblica per la licenza è indicata nel campo Il tuo codice licenza per questa applicazione.

Per aggiungere la chiave pubblica alla tua applicazione, basta copiare/incollare la stringa della chiave dal campo nell'applicazione come valore della variabile String BASE64_PUBLIC_KEY. Durante la copia, assicurati di aver selezionato l'intera stringa di chiave, senza omettere alcun carattere.

Ecco un esempio dall'applicazione LVL di esempio:

Kotlin

private const val BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... " //truncated for this example
class LicensingActivity : AppCompatActivity() {
    ...
}

Java

public class MainActivity extends Activity {
    private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example
    ...
}

Chiama il metodo onDestroy() di LicenseChecker per chiudere le connessioni IPC

Infine, per consentire la pulizia dell'LVL prima che l'applicazione Context venga modificata, aggiungi una chiamata al metodo onDestroy() di LicenseChecker dall'implementazione onDestroy() dell'attività. La chiamata fa sì che LicenseChecker chiuda correttamente qualsiasi connessione IPC aperta al servizio ILicensingService dell'applicazione Google Play e rimuovi qualsiasi riferimento locale al servizio e al gestore.

La mancata chiamata del metodo onDestroy() di LicenseChecker può causare problemi durante il ciclo di vita dell'applicazione. Ad esempio, se l'utente cambia l'orientamento dello schermo mentre è attivo un controllo delle licenze, l'applicazione Context viene eliminata. Se l'applicazione non chiude correttamente la connessione IPC di LicenseChecker, l'applicazione si arresta in modo anomalo alla ricezione della risposta. Analogamente, se l'utente esce dall'applicazione mentre è in corso un controllo della licenza, l'applicazione si arresta in modo anomalo alla ricezione della risposta, a meno che non abbia chiamato correttamente il metodo onDestroy() di LicenseChecker per disconnettersi dal servizio.

Ecco un esempio dall'applicazione di esempio inclusa nel LVL, dove mChecker è l'istanza LicenseChecker:

Kotlin

    override fun onDestroy() {
        super.onDestroy()
        checker.onDestroy()
        ...
    }

Java

    @Override
    protected void onDestroy() {
        super.onDestroy();
        checker.onDestroy();
        ...
    }

Se stai estendendo o modificando LicenseChecker, potresti anche dover chiamare il metodo finishCheck() di LicenseChecker per ripulire eventuali connessioni IPC aperte.

Implementazione di un DeviceLimiter

In alcuni casi, è consigliabile che Policy limiti il numero di dispositivi effettivi a cui è consentito utilizzare una singola licenza. In questo modo si impedisce a un utente di spostare un'applicazione concessa in licenza su una serie di dispositivi e di utilizzarla su tali dispositivi con lo stesso ID account. Inoltre impedirebbe a un utente di "condividere" l'applicazione fornendo ad altri individui i dati dell'account associati alla licenza, che potrebbero quindi accedere a tale account sui propri dispositivi e accedere alla licenza dell'applicazione.

L'LVL supporta le licenze per dispositivo fornendo un'interfaccia DeviceLimiter, che dichiara un singolo metodo, allowDeviceAccess(). Quando un LicenseValidator gestisce una risposta del server di licenze, chiama allowDeviceAccess(), trasmettendo una stringa User-ID estratta dalla risposta.

Se non vuoi supportare la limitazione dei dispositivi, non è necessario alcun intervento: la classe LicenseChecker utilizza automaticamente un'implementazione predefinita denominata NullDeviceLimiter. Come suggerisce il nome, NullDeviceLimiter è una classe "no-op" il cui metodo allowDeviceAccess() restituisce semplicemente una risposta LICENSED per tutti gli utenti e i dispositivi.

Attenzione:le licenze per dispositivo non sono consigliate per la maggior parte delle applicazioni perché:

  • Occorre fornire un server di backend per gestire la mappatura di utenti e dispositivi.
  • inavvertitamente.

Offuscamento del codice

Per garantire la sicurezza della tua applicazione, in particolare per un'applicazione a pagamento che utilizza licenze e/o vincoli e protezioni personalizzati, è molto importante offuscare il codice dell'applicazione. Offuscare correttamente il codice rende più difficile per un utente malintenzionato decompilare il bytecode dell'applicazione, modificarlo, ad esempio rimuovendo il controllo della licenza, e ricompilarlo.

Per le applicazioni Android sono disponibili diversi programmi di offuscamento, tra cui ProGuard, che offre anche funzionalità di ottimizzazione del codice. L'utilizzo di ProGuard o di un programma simile per offuscare il codice è vivamente consigliato per tutte le applicazioni che utilizzano licenze di Google Play.

Pubblicazione di un'applicazione concessa in licenza

Al termine del test dell'implementazione della licenza, puoi pubblicare l'applicazione su Google Play. Segui i normali passaggi per preparare, firmare e pubblicare l'applicazione.

Dove ricevere assistenza

In caso di domande o in caso di problemi durante l'implementazione o il deployment della pubblicazione nelle tue applicazioni, utilizza le risorse di assistenza elencate nella tabella di seguito. Inserendo le tue query al forum corretto, puoi ricevere più rapidamente l'assistenza di cui hai bisogno.

Tabella 2. Risorse di assistenza per gli sviluppatori per il servizio di licenze di Google Play.

Tipo di assistenza Risorsa Gamma di argomenti
Problemi di sviluppo e verifica Google Gruppi: android-developers download e integrazione LVL, progetti di libreria, Policy domande, idee per l'esperienza utente, gestione delle risposte, Obfuscator, IPC, configurazione dell'ambiente di test
Stack Overflow: http://stackoverflow.com/questions/restricted/android
Problemi relativi ad account, pubblicazione e deployment Forum di assistenza di Google Play Account editore, coppia di chiavi di licenza, account di test, risposte del server, risposte di test, deployment e risultati dell'applicazione
Domande frequenti sull'assistenza per le licenze di mercato
Issue Tracker LVL Strumento di monitoraggio dei problemi del progetto di concessione in licenza Segnalazioni di bug e problemi specifiche per le classi del codice sorgente LVL e le implementazioni dell'interfaccia

Per informazioni generali su come pubblicare post per i gruppi elencati sopra, consulta la sezione Risorse della community nella pagina Risorse di assistenza per gli sviluppatori.

Risorse aggiuntive

L'applicazione di esempio inclusa con l'LVL fornisce un esempio completo di come avviare un controllo delle licenze e gestire il risultato, nella classe MainActivity.