-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Query licenses from multiple Google accounts in a row
- Loading branch information
Showing
2 changed files
with
271 additions
and
190 deletions.
There are no files selected for viewing
202 changes: 202 additions & 0 deletions
202
vending-app/src/main/java/com/android/vending/licensing/LicenseChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
package com.android.vending.licensing; | ||
|
||
import static android.accounts.AccountManager.KEY_AUTHTOKEN; | ||
import static android.os.Binder.getCallingUid; | ||
|
||
|
||
import android.accounts.Account; | ||
import android.accounts.AccountManager; | ||
import android.accounts.AuthenticatorException; | ||
import android.accounts.OperationCanceledException; | ||
import android.content.pm.PackageInfo; | ||
import android.content.pm.PackageManager; | ||
import android.os.RemoteException; | ||
import android.util.Log; | ||
|
||
import com.android.vending.V1Container; | ||
import com.android.volley.Request; | ||
import com.android.volley.RequestQueue; | ||
import com.android.volley.Response; | ||
|
||
import java.io.IOException; | ||
|
||
import kotlin.Unit; | ||
|
||
/** | ||
* Performs license check including caller UID verification, using a given account, for which | ||
* an auth token is fetched. | ||
* | ||
* @param <D> Request parameter data value type | ||
* @param <R> Result type | ||
*/ | ||
public abstract class LicenseChecker<D, R> { | ||
|
||
private static final String TAG = "FakeLicenseChecker"; | ||
|
||
/* Possible response codes for checkLicense v1, from | ||
* https://developer.android.com/google/play/licensing/licensing-reference#server-response-codes and | ||
* the LVL library. | ||
*/ | ||
|
||
/** | ||
* The application is licensed to the user. The user has purchased the application, or is authorized to | ||
* download and install the alpha or beta version of the application. | ||
*/ | ||
static final int LICENSED = 0x0; | ||
/** | ||
* The application is licensed to the user, but there is an updated application version available that is | ||
* signed with a different key. | ||
*/ | ||
static final int NOT_LICENSED = 0x1; | ||
/** | ||
* The application is not licensed to the user. | ||
*/ | ||
static final int LICENSED_OLD_KEY = 0x2; | ||
/** | ||
* Server error — the application (package name) was not recognized by Google Play. | ||
*/ | ||
static final int ERROR_NOT_MARKET_MANAGED = 0x3; | ||
/** | ||
* Server error — the server could not load the application's key pair for licensing. | ||
*/ | ||
static final int ERROR_SERVER_FAILURE = 0x4; | ||
static final int ERROR_OVER_QUOTA = 0x5; | ||
|
||
/** | ||
* Local error — the Google Play application was not able to reach the licensing server, possibly because | ||
* of network availability problems. | ||
*/ | ||
static final int ERROR_CONTACTING_SERVER = 0x101; | ||
/** | ||
* Local error — the application requested a license check for a package that is not installed on the device. | ||
*/ | ||
static final int ERROR_INVALID_PACKAGE_NAME = 0x102; | ||
/** | ||
* Local error — the application requested a license check for a package whose UID (package, user ID pair) | ||
* does not match that of the requesting application. | ||
*/ | ||
static final int ERROR_NON_MATCHING_UID = 0x103; | ||
|
||
static final String AUTH_TOKEN_SCOPE = "oauth2:https://www.googleapis.com/auth/googleplay"; | ||
|
||
public void checkLicense(Account account, AccountManager accountManager, | ||
String packageName, PackageManager packageManager, | ||
RequestQueue queue, D queryData, | ||
BiConsumerWithException<Integer, R, RemoteException> onResult) | ||
throws RemoteException { | ||
try { | ||
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0); | ||
int versionCode = packageInfo.versionCode; | ||
|
||
// Verify caller identity | ||
if (packageInfo.applicationInfo.uid != getCallingUid()) { | ||
Log.e(TAG, | ||
"an app illegally tried to request licenses for another app (caller: " + getCallingUid() + ")"); | ||
onResult.accept(ERROR_NON_MATCHING_UID, null); | ||
} else { | ||
|
||
accountManager.getAuthToken( | ||
account, AUTH_TOKEN_SCOPE, false, | ||
future -> { | ||
try { | ||
String auth = future.getResult().getString(KEY_AUTHTOKEN); | ||
Request<?> request = createRequest(packageName, auth, | ||
versionCode, queryData, (Integer integer, R r) -> { | ||
|
||
try { | ||
onResult.accept(integer, r); | ||
} catch (RemoteException e) { | ||
Log.e(TAG, | ||
"After telling it the license check result, remote threw an Exception."); | ||
e.printStackTrace(); | ||
} | ||
}, error -> { | ||
Log.e(TAG, "license request failed with " + error.toString()); | ||
safeSendResult(onResult, ERROR_CONTACTING_SERVER, null); | ||
}); | ||
request.setShouldCache(false); | ||
queue.add(request); | ||
} catch (AuthenticatorException | IOException | OperationCanceledException e) { | ||
safeSendResult(onResult, ERROR_CONTACTING_SERVER, null); | ||
} | ||
|
||
}, null); | ||
} | ||
} catch (PackageManager.NameNotFoundException e) { | ||
Log.e(TAG, "an app tried to request licenses for package " + packageName + ", which does not exist"); | ||
onResult.accept(ERROR_INVALID_PACKAGE_NAME, null); | ||
} | ||
} | ||
|
||
private static <A, B, T extends Exception> void safeSendResult( | ||
BiConsumerWithException<A, B, T> consumerWithException, A a, B b) { | ||
try { | ||
consumerWithException.accept(a, b); | ||
} catch (Exception e) { | ||
Log.e(TAG, "While sending result " + a + ", " + b + ", remote encountered an exception."); | ||
e.printStackTrace(); | ||
} | ||
} | ||
|
||
public abstract Request<?> createRequest(String packageName, String auth, int versionCode, D data, BiConsumer<Integer, R> then, Response.ErrorListener errorListener); | ||
|
||
// Functional interfaces | ||
|
||
interface BiConsumerWithException<A, B, T extends Exception> { | ||
void accept(A a, B b) throws T; | ||
} | ||
|
||
interface BiConsumer<A, B> { | ||
void accept(A a, B b); | ||
} | ||
|
||
static class Tuple<A, B> { | ||
public final A a; | ||
public final B b; | ||
|
||
public Tuple(A a, B b) { | ||
this.a = a; | ||
this.b = b; | ||
} | ||
} | ||
|
||
// Implementations | ||
|
||
public static class V1 extends LicenseChecker<Long, Tuple<String, String>> { | ||
|
||
@Override | ||
public Request<V1Container> createRequest(String packageName, String auth, int versionCode, Long nonce, BiConsumer<Integer, Tuple<String, String>> then, | ||
Response.ErrorListener errorListener) { | ||
return new LicenseRequest.V1( | ||
packageName, auth, versionCode, nonce, response -> { | ||
if (response != null) { | ||
Log.v(TAG, "licenseV1 result was " + response.result + " with signed data " + | ||
response.signedData); | ||
|
||
if (response.result != null) { | ||
then.accept(response.result, new Tuple<>(response.signedData, response.signature)); | ||
} else { | ||
then.accept(LICENSED, new Tuple<>(response.signedData, response.signature)); | ||
} | ||
} | ||
}, errorListener | ||
); | ||
} | ||
} | ||
|
||
public static class V2 extends LicenseChecker<Unit, String> { | ||
@Override | ||
public Request<String> createRequest(String packageName, String auth, int versionCode, Unit data, | ||
BiConsumer<Integer, String> then, Response.ErrorListener errorListener) { | ||
return new LicenseRequest.V2( | ||
packageName, auth, versionCode, response -> { | ||
if (response != null) { | ||
then.accept(LICENSED, response); | ||
} else { | ||
then.accept(NOT_LICENSED, null); | ||
} | ||
}, errorListener | ||
); | ||
} | ||
} | ||
} |
Oops, something went wrong.