Adicionar a verificação de licenças do lado do cliente ao app

Aviso: quando o app realiza o processo de verificação de licença do lado do cliente, a modificação ou remoção da lógica associada a esse processo por possíveis invasores é facilitada.

Por isso, é altamente recomendável fazer a verificação de licenças do lado do servidor.

Depois de configurar uma conta de editor e um ambiente para desenvolvedores (consulte Configurar para o licenciamento), use a biblioteca de Verificação de Licença (LVL, na sigla em inglês) para adicionar a verificação de licença ao app.

A adição da verificação de licença com a LVL envolve as seguintes tarefas:

  1. Adicionar a permissão de licenciamento ao manifesto do aplicativo.
  2. Implementar uma Policy: você pode escolher uma das implementações completas fornecidas na LVL ou criar uma.
  3. Implementar um Obfuscator caso sua Policy armazene todos os dados de resposta de licença em cache.
  4. Adicionar código para verificar a licença na Activity principal do aplicativo.
  5. Implementar um DeviceLimiter (opcional e não recomendado para a maioria dos aplicativos).

As seções abaixo descrevem essas tarefas. Depois de concluir a integração, você poderá compilar o aplicativo e começar os testes, como descrito em Configurar o ambiente de testes.

Para ter uma visão geral do conjunto de arquivos de origem incluídos na LVL, consulte o Resumo de classes e interfaces da LVL.

Adicionar a permissão de licenciamento

Para que o aplicativo Google Play envie uma verificação de licença ao servidor, seu aplicativo precisa solicitar a permissão adequada, com.android.vending.CHECK_LICENSE. Se o aplicativo não declarar a permissão de licenciamento, mas tentar iniciar uma verificação de licença, a LVL emitirá uma exceção de segurança.

Para solicitar a permissão de licenciamento no seu aplicativo, declare um elemento <uses-permission> como filho de <manifest>, da seguinte maneira:

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

Por exemplo, veja como o aplicativo de amostra da LVL declara a permissão:

<?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>

Observação: não é possível declarar a permissão CHECK_LICENSE no manifesto do projeto de biblioteca da LVL, já que as ferramentas do SDK não integram esse manifesto àqueles dos aplicativos dependentes. Em vez disso, você precisa declarar a permissão em cada manifesto de aplicativo dependente.

Implementar uma política

O serviço de licenciamento do Google Play em si não determina se um usuário específico com uma licença específica tem acesso ao aplicativo. Essa responsabilidade é de uma implementação Policy fornecida no aplicativo.

Policy é uma interface declarada pela LVL e projetada para manter a lógica do aplicativo para permitir ou impedir o acesso do usuário com base no resultado de uma verificação de licença. Para usar a LVL, seu aplicativo precisa fornecer uma implementação de Policy.

A interface Policy declara dois métodos, allowAccess() e processServerResponse(), que são chamados por uma instância de LicenseChecker ao processar uma resposta do servidor de licença. Ela também declara um enum chamado LicenseResponse, que especifica o valor de resposta de licença transmitido em chamadas para processServerResponse().

  • processServerResponse() permite pré-processar os dados de resposta brutos recebidos do servidor de licenciamento antes de determinar se o acesso será concedido.

    Uma implementação típica extrai alguns ou todos os campos da resposta de licença e salva os dados localmente em um armazenamento permanente, como o da SharedPreferences, para garantir que os dados sejam acessíveis nas invocações do aplicativo e nas reinicializações do dispositivo. Por exemplo, uma Policy manteria o carimbo de data/hora da última verificação de licença, a contagem de tentativas, o período de validade da licença e informações semelhantes em um armazenamento persistente em vez de redefinir os valores sempre que o aplicativo fosse aberto.

    Ao armazenar dados de resposta localmente, a Policy precisa garantir que os dados sejam ofuscados. Consulte abaixo Como implementar um Obfuscator.

  • allowAccess() determina se o acesso do usuário ao aplicativo será concedido ou não com base em quaisquer dados de resposta de licença disponíveis (do servidor de licenciamento ou do cache) ou outras informações específicas do aplicativo. Por exemplo, sua implementação de allowAccess() pode considerar outros critérios, como uso ou outros dados recuperados de um servidor back-end. Em todos os casos, uma implementação de allowAccess() só retornará true se o usuário tiver licença para usar o aplicativo, conforme determinado pelo servidor de licenciamento, ou se houver um problema temporário na rede ou no sistema que impeça a conclusão da verificação de licença. Nesses casos, sua implementação pode manter uma contagem de respostas de tentativas e permitir provisoriamente o acesso até que a próxima verificação de licença seja concluída.

Para simplificar o processo de adição de licenciamento ao aplicativo e fornecer uma ilustração de como uma Policy precisa ser projetada, a LVL inclui duas implementações de Policy completas que você pode usar sem modificações ou adaptar às suas necessidades:

  • ServerManagedPolicy: uma Policy flexível que usa configurações do servidor e respostas armazenadas em cache para gerenciar o acesso em várias condições de rede.
  • StrictPolicy: que não armazena dados de resposta em cache e permite acesso apenas se o servidor tem uma resposta licenciada.

Para a maioria dos aplicativos, o uso da ServerManagedPolicy é altamente recomendado. A ServerManagedPolicy é o padrão da LVL e está integrada ao aplicativo de amostra da LVL.

Diretrizes para políticas personalizadas

Na implementação de licenciamento, você pode usar uma das políticas completas fornecidas na LVL (ServerManagedPolicy ou StrictPolicy) ou criar uma política personalizada. Para qualquer tipo de política personalizada, você precisa entender e considerar vários pontos de design importantes na implementação.

O servidor de licenciamento aplica limites gerais de solicitação para evitar o uso excessivo de recursos que possam resultar na negação de serviço. Quando um aplicativo excede o limite de solicitação, o servidor de licenciamento retorna uma resposta 503, que é transmitida ao aplicativo como um erro geral de servidor. Isso significa que nenhuma resposta de licença ficará disponível para o usuário até que o limite seja redefinido, o que pode afetar o usuário por um período indefinido.

Se você estiver criando uma política personalizada, recomendamos que a Policy:

  1. armazene em cache (e ofusque de forma adequada) a solicitação de resposta de licença mais recente no armazenamento persistente local;
  2. retorne a resposta armazenada em cache (pelo tempo que for válida) para todas as verificações de licença, em vez de fazer uma solicitação ao servidor de licenciamento. É altamente recomendado definir a validade da resposta de acordo com o VT extra fornecido pelo servidor. Consulte Extras de resposta do servidor para mais informações;
  3. Use um período de espera exponencial se novas tentativas causarem erros. O cliente do Google Play repete automaticamente as solicitações com falha, portanto, na maioria dos casos, não é necessário que Policy tente novamente;
  4. forneça um "período de carência" que permita que o usuário acesse o aplicativo por um tempo limitado ou número de usos enquanto uma verificação de licença está sendo testada novamente. O período de carência beneficia o usuário ao permitir o acesso até que a nova verificação de licença seja concluída e beneficia você ao estabelecer um limite rígido de acesso ao aplicativo quando não houver uma resposta de licença válida disponível.

Projetar seu Policy de acordo com as diretrizes listadas acima é fundamental, porque garante a melhor experiência possível para os usuários, oferecendo controle efetivo sobre o aplicativo mesmo em condições de erro.

Observe que qualquer Policy pode usar as configurações fornecidas pelo servidor de licenciamento para ajudar a gerenciar a validade e o armazenamento em cache, o período de carência da nova tentativa e muito mais. Extrair as configurações fornecidas pelo servidor é bastante simples, e o uso delas é altamente recomendado. Consulte a implementação ServerManagedPolicy para ver um exemplo de como extrair e usar os extras. Para ver uma lista de configurações do servidor e informações sobre como elas devem usadas, consulte Extras de resposta do servidor.

ServerManagedPolicy

A LVL inclui uma implementação completa e recomendada da interface Policy chamada ServerManagedPolicy. Ela é integrada às classes da LVL e serve como Policy padrão na biblioteca.

A ServerManagedPolicy fornece todo o gerenciamento necessário para respostas de licenças e de novas tentativas. Ela armazena todos os dados de resposta localmente em um arquivo SharedPreferences, ofuscando-o com a implementação Obfuscator do aplicativo. Isso garante que os dados de resposta da licença sejam seguros e persistam durante a reinicialização do dispositivo. A ServerManagedPolicy fornece implementações concretas dos métodos de interface processServerResponse() e allowAccess(), além de um conjunto de métodos e tipos de apoio para gerenciar respostas de licença.

Um recurso importante da ServerManagedPolicy é o uso de configurações fornecidas pelo servidor como base para gerenciar o licenciamento durante o período de reembolso de um aplicativo e em diferentes condições de rede e erro. Quando um aplicativo entra em contato com o servidor do Google Play para verificar a licença, o servidor anexa diversas configurações como pares de chave-valor no campo de extras de certos tipos de resposta de licença. Por exemplo, o servidor fornece os valores recomendados para o período de validade da licença do aplicativo, o período de carência e a contagem máxima permitida de novas tentativas, entre outras. A ServerManagedPolicy extrai os valores da resposta de licença no método processServerResponse() e os verifica no allowAccess(). Para ver uma lista das configurações do servidor usadas pela ServerManagedPolicy, consulte Extras de resposta do servidor.

Para conveniência, melhor performance e benefício de usar as configurações de licença do servidor do Google Play, o uso da ServerManagedPolicy como sua Policy de licenciamento é altamente recomendado.

Se estiver preocupado com a segurança dos dados de resposta de licença armazenados localmente em SharedPreferences, você pode usar um algoritmo de ofuscação mais forte ou criar uma Policy mais rigorosa que não armazena dados de licença. A LVL inclui um exemplo de uma Policy assim. Consulte StrictPolicy para mais informações.

Para usar a ServerManagedPolicy, importe-a para sua Activity, crie uma instância e transmita uma referência para a instância quando estiver criando seu LicenseChecker. Para saber mais, consulte Instanciar LicenseChecker e LicenseCheckerCallback.

StrictPolicy

A LVL inclui uma implementação alternativa completa da interface Policy chamada StrictPolicy. A implementação StrictPolicy fornece uma política mais restritiva do que a ServerManagedPolicy, já que não permite que o usuário acesse o aplicativo quando não recebe uma resposta de licença do servidor indicando que o usuário é licenciado no momento do acesso.

O principal recurso da StrictPolicy é que ela não armazena nenhum dado de resposta de licença localmente em um armazenamento persistente. Como nenhum dado é armazenado, as solicitações de novas tentativas não são rastreadas, e as respostas em cache não podem ser usadas para realizar verificações de licença. A Policy só permite o acesso quando:

  • a resposta da licença é recebida do servidor de licenciamento;
  • A resposta da licença indica que o usuário está licenciado para acessar o aplicativo.

O uso da StrictPolicy é adequado quando sua preocupação principal é garantir, em todos os casos possíveis, que um usuário terá permissão para acessar o aplicativo apenas se a licença for confirmada no momento do uso. Além disso, a política oferece um pouco mais de segurança do que a ServerManagedPolicy, já que, como não há dados armazenados localmente, um usuário mal-intencionado não pode adulterar dados em cache ou acessar o aplicativo.

Ao mesmo tempo, esse Policy apresenta um desafio para os usuários normais, já que, com ele, os usuários não podem acessar o aplicativo sem uma conexão de rede (celular ou Wi-Fi) disponível. Outro efeito colateral é que o aplicativo enviará mais solicitações de verificação de licença ao servidor, já que não é possível usar uma resposta armazenada em cache.

No geral, essa política representa a troca da conveniência do usuário pela segurança absoluta e o controle sobre o acesso. Considere essa troca cuidadosamente antes de usar essa Policy.

Para usar a StrictPolicy, importe-a para sua Activity, crie uma instância e transmita uma referência para ela durante a criação do seu LicenseChecker. Para saber mais, consulte Instanciar LicenseChecker e LicenseCheckerCallback.

Uma implementação típica da Policy precisa salvar os dados de resposta de licença para um aplicativo em um armazenamento permanente. Assim, ela pode ser acessada em invocações de aplicativos e reinicializações de dispositivos. Por exemplo, uma Policy manteria o carimbo de data/hora da última verificação de licença, a contagem de tentativas, o período de validade da licença e informações semelhantes em um armazenamento persistente em vez de redefinir os valores sempre que o aplicativo fosse aberto. O Policy padrão incluído na LVL, ServerManagedPolicy, armazena dados de resposta de licença em uma instância de SharedPreferences para garantir que os dados sejam persistentes.

Como o Policy usará dados de resposta de licença para determinar a permissão do acesso ao aplicativo, ela precisa garantir que todos os dados armazenados sejam seguros e não possam ser reutilizados ou manipulados por um usuário raiz em um dispositivo. O Policy precisa sempre ofuscar os dados antes de armazená-los, usando uma chave exclusiva para o aplicativo e o dispositivo. Isso é fundamental, já que evita que os dados ofuscados sejam compartilhados entre aplicativos e dispositivos.

A LVL ajuda o aplicativo a armazenar os dados de resposta de licença de maneira segura e persistente. Primeiro, ela fornece uma interface de Obfuscator que permite que o aplicativo use um algoritmo de ofuscação escolhido por ele em dados armazenados. Com base nisso, a LVL fornece a classe auxiliar PreferenceObfuscator, que faz a maior parte do trabalho de chamar a classe Obfuscator do aplicativo e ler e gravar os dados ofuscados em uma instância do SharedPreferences.

A LVL fornece uma implementação de Obfuscator completa, chamada AESObfuscator, que usa codificação AES para ofuscar dados. Você pode usar a AESObfuscator no aplicativo sem modificações ou pode adaptá-la às suas necessidades. Se você estiver usando uma Policy (como ServerManagedPolicy) que armazena dados de resposta de licença em cache, é altamente recomendado usar a AESObfuscator como base para sua implementação de Obfuscator. Para saber mais, consulte a seção a seguir.

AESObfuscator

A LVL inclui uma implementação completa e recomendada da interface Obfuscator chamada AESObfuscator. Ela é integrada ao aplicativo de amostra da LVL e serve como Obfuscator padrão na biblioteca.

A AESObfuscator fornece ofuscação segura de dados usando AES para criptografar e descriptografar os dados à medida que eles são gravados ou lidos no armazenamento. O Obfuscator propaga a criptografia usando três campos de dados fornecidos pelo aplicativo:

  1. Um sal: uma matriz de bytes aleatórios que serão usados em cada (des)ofuscação.
  2. Uma string identificadora de aplicativo, normalmente o nome do pacote do aplicativo.
  3. Uma string identificadora de dispositivo derivada do maior número possível de fontes específicas do dispositivo para torná-la única.

Para usar a AESObfuscator, importe-a para sua atividade. Declare uma matriz final estática particular para segurar os bytes do sal e inicialize-a com 20 bytes gerados aleatoriamente.

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

Em seguida, declare uma variável para manter um identificador de dispositivo e gere um valor para ele da maneira necessária. Por exemplo, o aplicativo de amostra incluído na LVL consulta as configurações do sistema de android.Settings.Secure.ANDROID_ID, que é exclusivo para cada dispositivo.

A depender das APIs usadas, seu aplicativo pode precisar solicitar outras permissões para conseguir informações específicas do dispositivo. Por exemplo, para consultar o TelephonyManager para conseguir o IMEI do dispositivo ou dados relacionados, o aplicativo também precisará solicitar a permissão android.permission.READ_PHONE_STATE no manifesto.

Antes de solicitar novas permissões com o único propósito de conseguir informações específicas do dispositivo para uso no Obfuscator, pense em como isso pode afetar o aplicativo ou a filtragem dele no Google Play, já que algumas permissões podem fazer com que as ferramentas de criação do SDK adicionem o <uses-feature> associado.

Por fim, construa uma instância de AESObfuscator, passando o sal, o identificador de aplicativo e o identificador de dispositivo. Você pode criar a instância diretamente durante a criação da Policy e do LicenseChecker. Por exemplo:

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

Para ver um exemplo completo, consulte MainActivity no aplicativo de amostra da LVL.

Como verificar a licença de uma atividade

Depois de implementar um Policy para gerenciar o acesso ao aplicativo, a próxima etapa é adicionar uma verificação de licença, que inicia uma consulta ao servidor de licenciamento, se necessário, e gerencia o acesso ao aplicativo com base na resposta de licença. Todo o trabalho de adicionar a verificação de licença e gerenciar a resposta ocorre no arquivo de origem Activity principal.

Para adicionar a verificação de licença e gerenciar a resposta, é necessário fazer o seguinte:

  1. Adicionar importações
  2. Implementar o LicenseCheckerCallback como uma classe interna particular
  3. Criar um gerenciador para postar do LicenseCheckerCallback na linha de execução de interface
  4. Instanciar o LicenseChecker e o LicenseCheckerCallback
  5. Chamar o checkAccess() para iniciar a verificação de licença
  6. Incorporar a chave pública de licenciamento
  7. Chamar o método onDestroy() do LicenseChecker para fechar as conexões IPC

As seções abaixo descrevem essas tarefas.

Visão geral da verificação e resposta de licença

Na maioria dos casos, você precisa adicionar a verificação de licença à Activity principal do aplicativo no método onCreate(). Isso garante que, quando o usuário abrir o aplicativo diretamente, a verificação de licença será invocada imediatamente. Em alguns casos, você também pode adicionar verificações de licença em outros locais. Por exemplo, caso seu aplicativo inclua vários componentes de atividade que possam ser iniciados por Intent por outros aplicativos, você pode adicionar verificações de licença a essas atividades.

Uma verificação de licença consiste em duas ações principais:

  • Uma chamada de método para iniciar a verificação de licença. Na LVL, a chamada é para o método checkAccess() de um objeto LicenseChecker criado por você.
  • Um callback que retorna o resultado da verificação de licença. Na LVL, isso é uma interface LicenseCheckerCallback que você implementa. A interface declara dois métodos, allow() e dontAllow(), que são invocados pela biblioteca com base no resultado da verificação de licença. Implemente esses dois métodos com qualquer lógica necessária para permitir ou impedir o acesso do usuário ao aplicativo. Esses métodos não determinam se o acesso será permitido. Essa decisão é de responsabilidade da implementação da Policy. Em vez disso, esses métodos simplesmente fornecem os comportamentos para que o aplicativo saiba como permitir e impedir o acesso (e como gerenciar os erros).

    Os métodos allow() e dontAllow() fornecem uma "razão" para a resposta, que pode ser um dos valores da Policy, LICENSED, NOT_LICENSED ou RETRY. Gerencie em especial o caso em que o método recebe a resposta RETRY para dontAllow() e fornece ao usuário um botão "Tentar de novo", o que pode acontecer por indisponibilidade do serviço durante a solicitação.

Figura 1. Visão geral de uma interação típica de verificação de licença.

O diagrama acima ilustra como uma verificação de licença típica ocorre:

  1. O código na atividade principal do aplicativo instancia os objetos LicenseCheckerCallback e LicenseChecker. Ao criar o LicenseChecker, o código passa o Context, uma implementação de Policy a ser usada e a chave pública da conta de editor para fazer o licenciamento como parâmetros.
  2. Depois disso, o código chama o método checkAccess() no objeto LicenseChecker. A implementação do método chama a Policy para determinar se há uma resposta de licença válida armazenada localmente em SharedPreferences.
    • Em caso afirmativo, a implementação de checkAccess() chama allow().
    • Caso contrário, o LicenseChecker inicia uma solicitação de verificação de licença que é enviada ao servidor de licenciamento.

    Observação: o servidor de licenciamento sempre retorna LICENSED quando você executa uma verificação de licença de um aplicativo em rascunho.

  3. Quando uma resposta é recebida, LicenseChecker cria um LicenseValidator que verifica os dados assinados da licença e extrai os campos da resposta, transmitindo-os à sua Policy para uma avaliação mais aprofundada.
    • Se a licença for válida, o Policy armazenará a resposta em SharedPreferences e notificará o validador, que chamará o método allow() no objeto LicenseCheckerCallback.
    • Se a licença não for válida, o Policy notificará o validador, que chamará o método dontAllow() no LicenseCheckerCallback.
  4. No caso de um erro recuperável local ou de servidor, como indisponibilidade da rede para enviar a solicitação, o LicenseChecker transmite uma resposta RETRY ao método processServerResponse() do objeto Policy.

    Além disso, os métodos de callback allow() e dontAllow() recebem um argumento reason. A razão do método allow() costuma ser Policy.LICENSED ou Policy.RETRY, e a razão dontAllow() costuma ser Policy.NOT_LICENSED ou Policy.RETRY. Esses valores de resposta são úteis para que você possa mostrar uma resposta adequada ao usuário, por exemplo, fornecendo um botão "Tentar de novo" quando dontAllow() responder com Policy.RETRY, o que pode acontecer por indisponibilidade do serviço.

  5. No caso de um erro de aplicativo, como a tentativa de verificar a licença de um nome de pacote inválido, o LicenseChecker transmite uma resposta de erro para o método applicationError() do LicenseCheckerCallback.

Além de iniciar a verificação de licença e gerenciar o resultado, descritos nas seções abaixo, seu aplicativo também precisará fornecer uma implementação de Policy e, se a Policy armazenar dados de resposta (como ServerManagedPolicy), uma implementação de Obfuscator.

Adicionar importações

Primeiro, abra o arquivo de classe da atividade principal do aplicativo e importe LicenseChecker e LicenseCheckerCallback do pacote da 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 você estiver usando a implementação padrão da Policy fornecida com a LVL, ServerManagedPolicy, importe-a junto com AESObfuscator. Se você estiver usando uma Policy personalizada ou Obfuscator, importe-as.

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;

Implementar o LicenseCheckerCallback como uma classe interna particular

LicenseCheckerCallback é uma interface fornecida pela LVL para gerenciar o resultado de uma verificação de licença. Para oferecer suporte ao licenciamento usando a LVL, implemente o LicenseCheckerCallback e os métodos relacionados para permitir ou impedir o acesso ao aplicativo.

O resultado de uma verificação de licença é sempre uma chamada para um dos métodos LicenseCheckerCallback, feita com base na validação da carga útil da resposta, no próprio código de resposta do servidor e em qualquer outro processamento fornecido pela Policy. Seu aplicativo pode implementar os métodos de qualquer maneira necessária. Em geral, é melhor manter os métodos simples, limitando-os ao gerenciamento do estado da IU e do acesso ao aplicativo. Se você quiser adicionar mais processamento de respostas de licença, como entrar em contato com um servidor de back-end ou aplicar restrições personalizadas, considere incorporar esse código à Policy em vez de colocá-lo nos métodos do LicenseCheckerCallback.

Na maioria dos casos, você precisa declarar a implementação de LicenseCheckerCallback como uma classe particular dentro da classe de atividade principal do aplicativo.

Implemente os métodos allow() e dontAllow() conforme necessário. Para começar, use comportamentos simples de gerenciamento de resultados nos métodos, como exibição do resultado da licença em uma caixa de diálogo. Isso ajuda a fazer com que o aplicativo seja executado mais rapidamente, além de ajudar na depuração. Depois de determinar os comportamentos exatos desejados, será possível adicionar um gerenciamento mais complexo.

Estas são algumas sugestões para gerenciar respostas não licenciadas em dontAllow():

  • Mostre uma caixa de diálogo "Tentar de novo" ao usuário, incluindo um botão para iniciar uma nova verificação de licença se a reason fornecida for Policy.RETRY.
  • Mostre uma caixa de diálogo "Comprar este aplicativo", incluindo um botão com um link direto para a página de detalhes do aplicativo no Google Play, onde o usuário pode comprá-lo. Para saber mais sobre como configurar esses links, consulte Criar links para seus produtos.
  • Mostre uma notificação de aviso que indique que os recursos do aplicativo estão limitados pela falta de licenciamento.

O exemplo abaixo mostra como o aplicativo de amostra da LVL implementa o LicenseCheckerCallback, com métodos que exibem o resultado da verificação de licença em uma caixa de diálogo.

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

Implemente também o método applicationError(), que a LVL chama para permitir que o aplicativo gerencie erros que não podem acionar novas tentativas. Para ver uma lista desses erros, consulte Códigos de resposta do servidor na Referência de licenciamento. Você pode implementar o método da maneira que precisar. Na maioria dos casos, o método precisa registrar o código do erro e chamar dontAllow().

Criar um gerenciador para postar do LicenseCheckerCallback na linha de execução de interface

Durante uma verificação de licença, a LVL passa a solicitação para o aplicativo Google Play que gerencia a comunicação com o servidor de licenciamento. A LVL passa a solicitação por IPC assíncrona (usando Binder) para que o processamento em si e a comunicação de rede não ocorram em uma linha de execução gerenciada pelo seu aplicativo. Da mesma forma, quando o aplicativo Google Play recebe o resultado, ele invoca um método de callback pela IPC, que, por sua vez, é executado em um pool de linhas de execução da IPC no processo do seu aplicativo.

A classe LicenseChecker gerencia a comunicação IPC do seu aplicativo com o aplicativo Google Play, incluindo a chamada que envia a solicitação e o callback que recebe a resposta. LicenseChecker também acompanha as solicitações de licença abertas e gerencia os tempos limite.

Para gerenciar os tempos limite adequadamente e processar as respostas recebidas sem afetar a linha de execução de interface do seu aplicativo, a LicenseChecker gera uma linha de execução em segundo plano na instanciação. Nessa linha de execução, ele processa todos os resultados da verificação de licença, seja uma resposta recebida do servidor ou um erro de tempo limite. Na conclusão do processamento, a LVL chama os métodos LicenseCheckerCallback da linha de execução em segundo plano.

Para o aplicativo, isso significa que:

  1. os métodos do LicenseCheckerCallback serão invocados, em muitos casos, em uma linha de execução em segundo plano;
  2. esses métodos não poderão atualizar o estado nem invocar qualquer processamento na linha de execução de interface, a menos que você crie um gerenciador nessa linha de execução e coloque os métodos nele.

Se você quiser que os métodos do LicenseCheckerCallback atualizem a linha de execução de interface, instancie um Handler no método onCreate() na atividade principal, como mostrado abaixo. Neste exemplo, os métodos LicenseCheckerCallback do aplicativo de amostra da LVL (ver acima) chamam displayResult() para atualizar a linha de execução de interface pelo método post() do gerenciador.

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

Em seguida, nos métodos LicenseCheckerCallback, você pode usar métodos do gerenciador para colocar objetos executáveis ou de mensagem nele. Veja como o aplicativo de amostra incluído na LVL coloca um executável em um gerenciador na linha de execução de interface para exibir o status da licença.

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

Instanciar o LicenseChecker e o LicenseCheckerCallback

No método onCreate() da atividade principal, crie instâncias particulares do LicenseCheckerCallback e do LicenseChecker. Você precisa instanciar LicenseCheckerCallback primeiro, porque será necessário passar uma referência a essa instância quando chamar o construtor para o LicenseChecker.

Quando você instancia o LicenseChecker, é necessário passar estes parâmetros:

  • O Context do aplicativo.
  • Uma referência à implementação da Policy que será usada na verificação de licença. Na maioria dos casos, a implementação padrão de Policy fornecida pela LVL, ServerManagedPolicy, será usada.
  • A variável string que contém a chave pública da sua conta de editor para fins de licenciamento.

Se você está usando a ServerManagedPolicy, não precisa acessar a classe diretamente para poder instanciá-la no construtor LicenseChecker, como mostrado no exemplo abaixo. Observe que você precisará passar uma referência para uma nova instância de Obfuscator ao criar a ServerManagedPolicy.

O exemplo abaixo mostra a instanciação de LicenseChecker e LicenseCheckerCallback no método onCreate() de uma classe de atividade.

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

Observe que LicenseChecker chama os métodos do LicenseCheckerCallback na linha de execução de interface somente quando há uma resposta de licença válida armazenada localmente. Se a verificação de licença for enviada ao servidor, os callbacks sempre serão originados da linha de execução em segundo plano, mesmo em casos de erro de rede.

Chamar o checkAccess() para iniciar a verificação de licença

Na atividade principal, adicione uma chamada ao método checkAccess() da instância LicenseChecker. Nessa chamada, passe uma referência para a instância do LicenseCheckerCallback como um parâmetro. Se precisar processar qualquer efeito especial da IU ou gerenciamento de estado antes da chamada, pode ser útil chamar checkAccess() de um método wrapper. Por exemplo, o aplicativo de amostra da LVL chama checkAccess() de um método 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);
    }

Incorporar a chave pública de licenciamento

Para cada aplicativo, o serviço do Google Play gera automaticamente um par de chaves pública/privada RSA de 2048 bits que é usado para licenciamento e faturamento no app. O par de chaves é exclusivamente associado ao aplicativo. Apesar disso, o par de chaves não é o mesmo nem derivado da chave usada para assinar seus aplicativos.

O Google Play Console expõe a chave pública para licenciamento de qualquer desenvolvedor conectado a ele, mas mantém a chave privada oculta de todos os usuários em um local seguro. Quando há uma verificação de licença solicitada para um aplicativo publicado na sua conta, o servidor de licenciamento assina a resposta de licença usando a chave privada do par de chaves do aplicativo. Quando a LVL recebe a resposta, ela usa a chave pública fornecida pelo aplicativo para verificar a assinatura da resposta de licença.

Para adicionar licenciamento a um aplicativo, você precisa ter a chave pública de licenciamento e copiá-la no seu aplicativo. Veja como encontrar a chave pública de licenciamento do seu aplicativo:

  1. Acesse o Google Play Console e faça login. O login precisa ser feito na conta que publicou ou publicará o aplicativo que está sendo licenciado.
  2. Na página de detalhes do aplicativo, localize o link Serviços e APIs e clique nele.
  3. Na página Serviços e APIs, localize a seção Licenciamento e faturamento no app. Sua chave pública de licenciamento está no campo Sua chave de licença para este aplicativo.

Para adicionar a chave pública ao aplicativo, copie/cole a string da chave desse campo no aplicativo como o valor da variável string BASE64_PUBLIC_KEY. Verifique se você selecionou toda a string da chave, sem omitir nenhum caractere.

Veja um exemplo do aplicativo de amostra da LVL:

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

Chamar o método onDestroy() do LicenseChecker para fechar as conexões IPC

Por fim, para permitir que a LVL seja limpa antes que o Context do aplicativo mude, adicione uma chamada ao método onDestroy() do LicenseChecker na implementação de onDestroy() da atividade. A chamada faz com que LicenseChecker feche corretamente qualquer conexão IPC aberta para o ILicensingService do aplicativo Google Play e remova todas as referências locais ao serviço e ao gerenciador.

Deixar de chamar o método onDestroy() do LicenseChecker pode levar a problemas no funcionamento do aplicativo. Por exemplo, se o usuário mudar a orientação da tela enquanto uma verificação de licença estiver ativa, o Context do aplicativo será destruído. Se o aplicativo não fechar corretamente a conexão IPC do LicenseChecker, ele travará quando receber a resposta. Da mesma forma, se o usuário sair do aplicativo enquanto uma verificação de licença estiver em andamento, o aplicativo falhará quando receber a resposta, a menos que tenha chamado adequadamente o método onDestroy() do LicenseChecker para se desconectar do serviço.

Veja um exemplo do aplicativo de amostra incluído na LVL, em que mChecker é a instância de LicenseChecker:

Kotlin

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

Java

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

Se você estiver estendendo ou modificando o LicenseChecker, vai precisar chamar o método finishCheck() do LicenseChecker para limpar quaisquer conexões IPC abertas.

Implementar um DeviceLimiter

Em alguns casos, é interessante que o Policy limite o número de dispositivos autorizados a usar uma única licença. Isso impede que um usuário transfira um aplicativo licenciado para vários dispositivos e use o aplicativo nesses dispositivos com o mesmo ID da conta. Isso também impede que um usuário "compartilhe" o aplicativo fornecendo as informações da conta associadas à licença a outras pessoas e permitindo que elas façam login e acessem a licença do aplicativo.

A LVL é compatível com o licenciamento por dispositivo, basta fornecer uma interface DeviceLimiter, que declara um único método, allowDeviceAccess(). Quando um LicenseValidator gerencia uma resposta do servidor de licenciamento, ele chama allowDeviceAccess(), passando uma string de ID de usuário extraída da resposta.

Se você não quiser o suporte à limitação de dispositivo, não faça nada. A classe LicenseChecker usa automaticamente uma implementação padrão chamada NullDeviceLimiter. Como o nome sugere, NullDeviceLimiter é uma classe de ambiente autônomo cujo método allowDeviceAccess() simplesmente retorna uma resposta LICENSED para todos os usuários e dispositivos.

Cuidado: o licenciamento por dispositivo não é recomendado para a maioria dos aplicativos, porque:

  • é necessário fornecer um servidor de back-end para gerenciar o mapeamento de usuários e dispositivos;
  • ele pode resultar no impedimento inadvertido do acesso de um usuário a um aplicativo que foi comprado em outro dispositivo.

Ofuscar seu código

Para garantir a segurança do seu aplicativo, principalmente se for um aplicativo pago que usa licenciamento e/ou restrições e proteções personalizadas, é muito importante ofuscar o código. Ofuscar o código de maneira adequada torna mais difícil para um usuário mal-intencionado descompilar o bytecode e modificar o aplicativo, por exemplo, removendo a verificação de licença, e recompile-o em seguida.

Vários programas de ofuscação estão disponíveis para apps Android, incluindo o ProGuard (link em inglês), que também oferece recursos de otimização de código. O uso do ProGuard ou de um programa semelhante para ofuscar o código é altamente recomendável para todos os aplicativos que usam o Licenciamento do Google Play.

Publicar um aplicativo licenciado

Quando terminar de testar a implementação da sua licença, você estará pronto para publicar o aplicativo no Google Play. Siga as etapas normais para preparar, assinar e publicar o aplicativo.

Como receber suporte

Se você tiver dúvidas ou encontrar problemas ao implementar ou implantar a publicação nos seus aplicativos, use os recursos de suporte listados na tabela abaixo. Ao enviar suas perguntas ao fórum correto, você pode conseguir o suporte de que precisa mais rapidamente.

Tabela 2. Recursos de suporte ao desenvolvedor para o serviço de Licenciamento do Google Play.

Tipo de atendimento Recurso Temas
Problemas com desenvolvimento e teste Grupos do Google: android-developers Download e integração da LVL, projetos de biblioteca, dúvidas sobre Policy, ideias de experiência do usuário, gerenciamento de respostas, Obfuscator, IPC, configuração do ambiente de testes
Stack Overflow: http://stackoverflow.com/questions/tagged/android (link em inglês)
Problemas com contas, publicação e implantação Fórum de Ajuda do Google Play Contas de editor, par de chaves de licenciamento, contas de teste, respostas de servidor, respostas de teste, implantação de aplicativos e resultados
Perguntas frequentes sobre suporte de licenciamento do mercado
Issue Tracker da LVL Issue Tracker do Marketlicensing Relatórios de bugs e problemas relacionados especificamente às classes do código-fonte e implementações de interface da LVL

Para ver informações gerais sobre como postar nos grupos listados acima, consulte a seção Recursos da comunidade na página Recursos de suporte para desenvolvedores.

Outros recursos

O aplicativo de amostra incluído na LVL fornece um exemplo completo de como iniciar uma verificação de licença e gerenciar o resultado na classe MainActivity.