ライセンス リファレンス

LVL のクラスとインターフェース

表 1 に、Android SDK で利用可能な License Verification Library(LVL)のすべてのソースファイルを示します。ファイルはすべて、com.android.vending.licensing パッケージに含まれます。

表 1. LVL のクラスとインターフェースの概要

カテゴリ 名前 説明
ライセンス チェックと結果 LicenseChecker ライセンス チェックを開始するためにインスタンス化(またはサブクラス化)するクラス。
LicenseCheckerCallback ライセンス チェックの結果を処理するために実装するインターフェース。
ポリシー Policy ライセンス応答に基づいて、アプリへのアクセスを許可するかどうかを判断するために実装するインターフェース。
ServerManagedPolicy デフォルトの Policy 実装。ライセンス サーバーから提供される設定を使用して、ライセンス データのローカル保存、ライセンスの有効性、再試行を管理します。
StrictPolicy Policy の代替実装。サーバーからの直接のライセンス応答に基づいたライセンス機能のみ行います。キャッシュへの保存やリクエストの再試行は行いません。
データの難読化
(任意)
Obfuscator ライセンス応答データを永続ストアに保存する Policy(ServerManagedPolicy など)を使用する場合に実装するインターフェース。難読化アルゴリズムを適用して、書き込みや読み取りの対象データのエンコードとデコードを行います。
AESObfuscator AES による暗号化と復号のアルゴリズムを使用してデータの難読化と難読化解除を行うデフォルトの Obfuscator 実装。
デバイスの制限
(任意)
DeviceLimiter アプリの使用を特定のデバイスに限定する場合に実装するインターフェース。LicenseValidator から呼び出されます。DeviceLimiter の実装は、バックエンド サーバーを必要とするうえ、慎重に設計しないとユーザーがライセンスを付与されたアプリにアクセスできなくなる可能性があるため、ほとんどのアプリには推奨されません。
NullDeviceLimiter デフォルトの DeviceLimiter 実装で、何も制限しません(すべてのデバイスにアクセスを許可します)。
ライブラリの主要部(統合は不要) ResponseData ライセンス応答のフィールドを保持するクラス。
LicenseValidator ライセンス サーバーから受信した応答を復号して検証するクラス。
ValidationException Obfuscator で管理されるデータの完全性を検証する際に発生するエラーを示すクラス。
PreferenceObfuscator システムの SharedPreferences ストアに対して難読化データの読み書きを行うユーティリティ クラス。
ILicensingService 一方向 IPC インターフェース。ライセンス チェック リクエストはこのインターフェースを介して Google Play クライアントに渡されます。
ILicenseResultListener 一方向 IPC コールバックの実装。アプリはこの実装を介してライセンス サーバーから非同期応答を受け取ります。

サーバー応答

表 2 に、ライセンス サーバーから返されるすべてのライセンス応答フィールドを示します。

表 2. Google Play サーバーから返されるライセンス応答フィールドの概要

項目名 説明
responseCode ライセンス サーバーから返される応答コード。レスポンス コードについては、サーバー レスポンス コードをご覧ください。
signedData ライセンス サーバーから返されたデータを保持する文字列の連結(例: responseCode|nonce|packageName|versionCode|userId|timestamp:extras)。
  • responseCode: ライセンス サーバーから返されるレスポンス コード。
  • nonce: リクエストのノンス ID。
  • packageName: ライセンスを確認するアプリのパッケージ名。
  • versionCode: ライセンスを確認するアプリのバージョン コード。
  • userId: アプリごとにユーザーの一意の ID を指定します。同じユーザーはアプリごとに異なる ID を取得します。
  • timestamp: 1970-01-01 00:00:00 UTC のエポックからリクエストまでのミリ秒数。
  • extras: アプリのライセンス管理に役立つ追加情報。追加フィールドについては、サーバー応答の付加情報で概説しています。
signature アプリ固有の鍵を使用した signedData の署名。

サーバー応答コード

表 3 に、ライセンス サーバーがサポートするすべてのライセンス応答コードを示します。通常、アプリではこれらの応答コードをすべて処理する必要があります。デフォルトでは、LVL の LicenseValidator クラスには、これらの応答コードに対する必要な処理がすべて含まれています。

表 3: Google Play サーバーからのライセンス応答で返される応答コードの概要

応答コード 整数値の表現 説明 署名付き 付加情報 コメント
LICENSED 0 ユーザーにアプリのライセンスが付与されています。ユーザーは、アプリを購入しているか、アプリのアルファ版またはベータ版をダウンロードしてインストールする権限を有しています。 はい VTGTGR Policy の制約に基づいてアクセスを許可します。
LICENSED_OLD_KEY 2 ユーザーにアプリのライセンスが付与されていますが、異なる鍵で署名された更新版のアプリがリリースされています。 はい VTGTGRUT Policy の制約に基づいて、必要に応じてアクセスを許可します。

インストールされているアプリのバージョンで使用している鍵ペアが無効である、または不正使用されている可能性があります。アプリでは、必要に応じてアクセスを許可できます。または、アップグレード可能であることをユーザーに通知し、アップグレードされるまでアクセスを制限することもできます。

NOT_LICENSED 1 ユーザーにアプリのライセンスが付与されていません。 × アクセスを許可しません。
ERROR_CONTACTING_SERVER 257 ローカルエラー。Google Play アプリからライセンス サーバーに通信できませんでした。おそらく、ネットワークを利用できないことが原因です。 × Policy の再試行制限内でライセンス チェックを再試行します。
ERROR_SERVER_FAILURE 4 サーバーエラー。サーバーがアプリのライセンス用鍵ペアを読み込めませんでした。 × Policy の再試行制限内でライセンス チェックを再試行します。
ERROR_INVALID_PACKAGE_NAME 258 ローカルエラー。デバイスにインストールされていないパッケージのライセンス チェックがアプリからリクエストされました。 いいえ ライセンス チェックを再試行しないでください。

通常、開発エラーが原因です。

ERROR_NON_MATCHING_UID 259 ローカルエラー。アプリからリクエストされたライセンス チェックの対象パッケージの UID(パッケージ、ユーザー ID のペア)が、リクエスト元アプリの UID と一致しません。 いいえ ライセンス チェックを再試行しないでください。

通常、開発エラーが原因です。

ERROR_NOT_MARKET_MANAGED 3 サーバーエラー。アプリ(パッケージ名)が Google Play で認識されませんでした。 × ライセンス チェックを再試行しないでください。

アプリが Google Play を介して公開されていないか、ライセンスの実装に開発エラーがある可能性があります。

注: テスト環境のセットアップに記載されているとおり、アプリ デベロッパーと登録済みテストユーザー向けの応答コードは、Google Play Console を使用して手動でオーバーライドできます。

注: 以前は、公開前のドラフト版をアップロードしてアプリをテストできました。この機能はサポートされなくなったため、代わりに、アルファ版またはベータ版の配布チャンネルにアプリを公開する必要があります。詳しくは、ドラフト版アプリのサポートの終了についての説明をご覧ください。

サーバー応答の付加情報

アプリの払い戻し期間全体にわたってアプリへのアクセスを管理できるようにアプリをサポートし、また、上記以外の情報を提供するために、ライセンス応答にはライセンス サーバーによっていくつかの情報が付加されます。具体的には、ライセンス サービスから、アプリのライセンス有効期間、再試行猶予期間、最大許容再試行回数などの推奨設定値が提供されます。アプリで APK 拡張ファイルを使用している場合は、応答にファイルの名前、サイズ、URL も含まれます。サーバーはこれらの設定を、ライセンス応答の「付加情報」フィールドに Key-Value ペアとして追加します。

Policy 実装では、ライセンス応答から付加情報の設定を抽出し、必要に応じて使用できます。LVL のデフォルトの Policy 実装(ServerManagedPolicy)は、そのまま機能する実装としても、付加情報設定の取得、保存、使用の方法を示す例としても利用できます。

表 4. Google Play サーバーからのライセンス応答で提供されるライセンス管理設定の概要

付加情報説明
VT ライセンス有効性タイムスタンプ。現在の(キャッシュに保存された)ライセンス応答の有効期限となり、ライセンス サーバーで再チェックする必要がある日時を示します。ライセンス有効期間については、下記のセクションをご覧ください。  
GT 猶予期間タイムスタンプ。Policy でアプリへのアクセスを許可できる期間の終了時を示します(応答ステータスが RETRY の場合も含む)。

値はサーバーによって管理されますが、通常は 5 日以上です。再試行期間と最大再試行回数については、下記のセクションをご覧ください。

GR 最大再試行回数。ユーザーによるアプリへのアクセスを拒否する前に、Policy で許可すべきライセンス チェックの連続 RETRY 数を示します。

値はサーバーによって管理されますが、通常は「10」以上です。再試行期間と最大再試行回数については、下記のセクションをご覧ください。

UT 更新タイムスタンプ。このアプリの最新版がアップロードされ公開された日時を示します。

この付加情報は、LICENSED_OLD_KEYS 応答でのみ返されます。Policy はこの情報を基に、ユーザーによるアプリへのアクセスを拒否する前に、新しいライセンス用鍵でアップデートが公開されてからどの程度経過しているかを確認できます。

FILE_URL1 または FILE_URL2 拡張ファイルの URL(1 はメインファイル、2 はパッチファイル)。HTTP でファイルをダウンロードする場合はこの URL を使用します。
FILE_NAME1 または FILE_NAME2 拡張ファイルの名前(1 はメインファイル、2 はパッチファイル)。ファイルをデバイスに保存する場合は、この名前を使用する必要があります。
FILE_SIZE1 または FILE_SIZE2 バイト単位のファイルサイズ(1 はメインファイル、2 はパッチファイル)。この情報により、ファイルをダウンロードする前にデバイスの共有ストレージに十分な空き容量があるかどうかを確認できます。

ライセンス有効期間

Google Play ライセンス サーバーにより、ダウンロードされたすべてのアプリに対してライセンス有効期間が設定されます。この期間は、アプリ内のライセンス Policy で、アプリのライセンス ステータスが不変かつキャッシュ保存可能とみみなすべき期間を表します。どのライセンス チェックに対する応答にも、ライセンス サーバーにより、付加情報のキー VT の値として有効期限のタイムスタンプが追加されます。これにより、応答から有効期間がわかります。Policy では、VT キーの値を抽出して使用することにより、有効期間が終了するまでという条件付きで、ライセンスを再チェックせずにアプリへのアクセスを許可できます。

ライセンス Policy でライセンス ステータスをライセンス サーバーに再チェックをリクエストする必要がある場合は、ライセンス有効性の通知があります。これは、実際にアプリ使用のライセンスが付与されているかどうかを示すものではありません。つまり、アプリのライセンス有効期間が終了しても、アプリにライセンスが付与されなくなったわけではありません。Policy でサーバーによるライセンス ステータスの再チェックをする必要があるというだけです。したがって、Policy では、最初のライセンス ステータスをローカルなキャッシュに保存し、ライセンス有効期間が終了するまでの間は、ライセンス チェックをサーバーに送信する代わりに、キャッシュに保存したライセンス ステータスを返すことができます。

ライセンス サーバーによる有効期間の管理は、Google Play で提供される有料アプリの払い戻し期間にわたって、アプリでライセンスを適切に適用できるようにするための手段となります。有効期間は、アプリが購入されたかどうか、また購入された場合はどれくらい前に購入されたかに基づいて設定されます。サーバーでの有効期間の設定は、具体的には次のように行われます。

  • 有料アプリの場合、サーバーは初期ライセンス有効期間を設定し、アプリが払い戻し可能な間はライセンス応答が有効なままになるようにします。これにより、アプリのライセンス Policy では、初期ライセンス チェックの結果をキャッシュに保存しておけば、その有効期間が終了するまで再度ライセンス チェックを行わずに済みます。
  • アプリの払い戻しができなくなると、サーバーはより長い有効期間(通常は数日)を設定します。
  • 無料アプリの場合、サーバーは有効期間を非常に大きい値(long.MAX_VALUE)に設定します。これにより、Policy では、有効期間のタイムスタンプをローカルなキャッシュに保存しておけば、以後アプリのライセンス ステータスを再チェックする必要がなくなります。

ServerManagedPolicy 実装では、抽出したタイムスタンプ(mValidityTimestamp)を、ユーザーにアプリへのアクセスを許可する前にライセンス ステータスをサーバーで再チェックするかどうか決めるための主要な条件として使用します。

再試行期間と最大再試行回数

場合によっては、システムやネットワークの状態により、アプリからのライセンス チェックがライセンス サーバーに届かない、またはサーバーからの応答が Google Play クライアント アプリに届かないことがあります。たとえば、ユーザーは、モバイル ネットワークやデータ接続が利用できない場合(飛行機内にいる場合など)や、ネットワーク接続が不安定だったり、モバイルデータ信号が弱かったりする場合にも、アプリを起動する可能性があります。

ネットワークの問題によってライセンス チェックが正常に行われない場合、Google Play クライアントから PolicyprocessServerResponse() メソッドに RETRY 応答コードが返され、アプリに通知されます。システムに問題がある場合(アプリが Google Play の ILicensingService 実装にバインドできない場合など)は、LicenseChecker ライブラリ自体が、RETRY 応答コードを使用して Policy の processServerResponse() メソッドを呼び出します。

通常、RETRY 応答コードは、ライセンス チェックの完了を妨げるエラーが発生したことをアプリに通知するものです。

Google Play サーバーにより設定される再試行「猶予期間」と推奨最大再試行回数に基づいて、アプリはエラー状態のライセンス機能を管理することができます。これらの値は、サーバーによって、すべてのライセンス チェック応答の付加情報として GT キーと GR キーに追加されます。

アプリの Policy では、付加情報 GTGR を抽出し、それらを使用して、次のように条件付きでアプリへのアクセスを許可できます。

  • ライセンス チェックの結果として RETRY 応答が返された場合は、PolicyRETRY 応答コードをキャッシュに保存し、RETRY 応答をカウントします。
  • 再試行猶予期間内であるか、最大再試行回数に達していない場合は、Policy でユーザーにアプリへのアクセスを許可します。

ServerManagedPolicy では、サーバーから提供される上記の GTGR の値を使用します。下記の例は、allow() メソッド内での RETRY 応答の条件付き処理を示します。RETRY 応答のカウントは、ここには示していませんが、processServerResponse() メソッド内で維持されます。

Kotlin

fun allowAccess(): Boolean {
    val ts = System.currentTimeMillis()
    return when(lastResponse) {
        LICENSED -> {
            // Check if the LICENSED response occurred within the validity timeout.
            ts <= validityTimestamp  // Cached LICENSED response is still valid.
        }
        RETRY -> {
            ts < lastResponseTime + MILLIS_PER_MINUTE &&
                    // Only allow access if we are within the retry period
                    // or we haven't used up our max retries.
                    (ts <= retryUntil || retryCount <= maxRetries)
        }
        else -> false
    }
}

Java

public boolean allowAccess() {
    long ts = System.currentTimeMillis();
    if (lastResponse == LicenseResponse.LICENSED) {
        // Check if the LICENSED response occurred within the validity timeout.
        if (ts <= validityTimestamp) {
            // Cached LICENSED response is still valid.
            return true;
        }
    } else if (lastResponse == LicenseResponse.RETRY &&
                ts < lastResponseTime + MILLIS_PER_MINUTE) {
        // Only allow access if we are within the retry period
        // or we haven't used up our max retries.
        return (ts <= retryUntil || retryCount <= maxRetries);
    }
    return false;
}