diff --git a/app/build.gradle b/app/build.gradle index ea82af196..4e1f4d2ec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'com.google.protobuf' android { compileSdkVersion 25 @@ -21,50 +20,8 @@ android { lintOptions { disable 'GoogleAppIndexingWarning','GoogleAppIndexingApiWarning' } - sourceSets { - debug { - java { - srcDirs 'src/main/java', "${buildDir}/generated/source/proto/debug/javalite" - } - } - release { - java { - srcDirs 'src/main/java', "${buildDir}/generated/source/proto/release/javalite" - } - } - main { - proto { - srcDir 'src/main/proto' - } - } - } } dependencies { - compile group: 'com.google.protobuf', name: 'protobuf-lite', version: '3.0.0' - compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.5.0' + compile 'com.github.yeriomin:play-store-api:2eee3e034b' } - -protobuf { - protoc { - artifact = 'com.google.protobuf:protoc:3.0.0' - } - plugins { - grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.0.1' - } - javalite { - artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' - } - } - generateProtoTasks { - all()*.plugins { - javalite { } - } - ofNonTest()*.plugins { - grpc { - option 'lite' - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/yeriomin/playstoreapi/DeviceInfoProvider.java b/app/src/main/java/com/github/yeriomin/playstoreapi/DeviceInfoProvider.java deleted file mode 100644 index 661d13425..000000000 --- a/app/src/main/java/com/github/yeriomin/playstoreapi/DeviceInfoProvider.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.yeriomin.playstoreapi; - -public interface DeviceInfoProvider { - - AndroidCheckinRequest generateAndroidCheckinRequest(); - DeviceConfigurationProto getDeviceConfigurationProto(); - String getUserAgentString(); - int getSdkVersion(); -} diff --git a/app/src/main/java/com/github/yeriomin/playstoreapi/GooglePlayAPI.java b/app/src/main/java/com/github/yeriomin/playstoreapi/GooglePlayAPI.java deleted file mode 100644 index 19f8b862d..000000000 --- a/app/src/main/java/com/github/yeriomin/playstoreapi/GooglePlayAPI.java +++ /dev/null @@ -1,436 +0,0 @@ -package com.github.yeriomin.playstoreapi; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.StringTokenizer; - -/** - * This class provides - * checkin, search, details, bulkDetails, browse, list and download - * capabilities. It uses Apache Commons HttpClient for POST and GET - * requests. - *

- *

- * XXX : DO NOT call checkin, login and download consecutively. To allow - * server to catch up, sleep for a while before download! (5 sec will do!) Also - * it is recommended to call checkin once and use generated android-id for - * further operations. - *

- * - * @author akdeniz - */ -public class GooglePlayAPI { - - private static final String SCHEME = "https://"; - private static final String HOST = "android.clients.google.com"; - private static final String CHECKIN_URL = SCHEME + HOST + "/checkin"; - private static final String URL_LOGIN = SCHEME + HOST + "/auth"; - private static final String C2DM_REGISTER_URL = SCHEME + HOST + "/c2dm/register2"; - private static final String FDFE_URL = SCHEME + HOST + "/fdfe/"; - private static final String LIST_URL = FDFE_URL + "list"; - private static final String BROWSE_URL = FDFE_URL + "browse"; - private static final String DETAILS_URL = FDFE_URL + "details"; - private static final String SEARCH_URL = FDFE_URL + "search"; - private static final String BULKDETAILS_URL = FDFE_URL + "bulkDetails"; - private static final String PURCHASE_URL = FDFE_URL + "purchase"; - private static final String REVIEWS_URL = FDFE_URL + "rev"; - private static final String UPLOADDEVICECONFIG_URL = FDFE_URL + "uploadDeviceConfig"; - private static final String RECOMMENDATIONS_URL = FDFE_URL + "rec"; - - private static final String ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE"; - - public enum REVIEW_SORT { - NEWEST(0), HIGHRATING(1), HELPFUL(2); - - public int value; - - REVIEW_SORT(int value) { - this.value = value; - } - } - - public enum RECOMMENDATION_TYPE { - ALSO_VIEWED(1), ALSO_INSTALLED(2); - - public int value; - - RECOMMENDATION_TYPE(int value) { - this.value = value; - } - } - - private String token; - private String gsfId; - private String email; - private String password; - private ThrottledOkHttpClient client; - private Locale locale; - private DeviceInfoProvider deviceInfoProvider; - private Map searchNextPage = new HashMap<>(); - - public void setToken(String token) { - this.token = token; - } - - public void setGsfId(String gsfId) { - this.gsfId = gsfId; - } - - public void setDeviceInfoProvider(DeviceInfoProvider deviceInfoProvider) { - this.deviceInfoProvider = deviceInfoProvider; - } - - private ThrottledOkHttpClient getClient() { - if (this.client == null) { - this.client = new ThrottledOkHttpClient(); - } - return this.client; - } - - public void setLocale(Locale locale) { - this.locale = locale; - } - - /** - * If this constructor is used, Android ID must be generated by calling - * checkin() or set by using setGsfId before - * using other abilities. - */ - public GooglePlayAPI(String email, String password) { - this.email = email; - this.password = password; - } - - /** - * Performs authentication on "ac2dm" service and match up android id, - * security token and email by checking them in on this server. - *

- * This function sets check-inded android ID and that can be taken either by - * using getToken() or from returned - * {@link AndroidCheckinResponse} instance. - */ - public String getGsfId() throws IOException { - AndroidCheckinRequest request = this.deviceInfoProvider.generateAndroidCheckinRequest(); - - // this first checkin is for generating android-id - AndroidCheckinResponse checkinResponse = checkin(request.toByteArray()); - this.gsfId = BigInteger.valueOf(checkinResponse.getAndroidId()).toString(16); - String securityToken = BigInteger.valueOf(checkinResponse.getSecurityToken()).toString(16); - - AndroidCheckinRequest.Builder checkInbuilder = AndroidCheckinRequest.newBuilder(request); - String AC2DMToken = getAC2DMToken(); - AndroidCheckinRequest build = checkInbuilder - .setId(new BigInteger(this.gsfId, 16).longValue()) - .setSecurityToken(new BigInteger(securityToken, 16).longValue()) - .addAccountCookie("[" + this.email + "]") - .addAccountCookie(AC2DMToken) - .build(); - // this is the second checkin to match credentials with android-id - checkin(build.toByteArray()); - return this.gsfId; - } - - /** - * Posts given check-in request content and returns - * {@link AndroidCheckinResponse}. - */ - private AndroidCheckinResponse checkin(byte[] request) throws IOException { - Map headers = getDefaultHeaders(); - headers.put("Content-Type", "application/x-protobuffer"); - byte[] content = getClient().post(CHECKIN_URL, request, headers); - return AndroidCheckinResponse.parseFrom(content); - } - - /** - * Authenticates on server with given email and password and sets - * authentication token. This token can be used to login instead of using - * email and password every time. - */ - public String getToken() throws IOException { - Map params = getDefaultLoginParams(); - params.put("service", "androidmarket"); - params.put("app", "com.android.vending"); - params.put("androidId", this.getGsfId()); - byte[] responseBytes = getClient().post(URL_LOGIN, params, getDefaultHeaders()); - Map response = parseResponse(new String(responseBytes)); - if (response.containsKey("Auth")) { - return response.get("Auth"); - } else { - throw new GooglePlayException("Authentication failed! (login)"); - } - } - - /** - * Logins AC2DM server and returns authentication string. - *

- *

- * client_sig is SHA1 digest of encoded certificate on - * GoogleLoginService(package name : com.google.android.gsf) system APK. - * But google doesn't seem to care of value of this parameter. - */ - public String getAC2DMToken() throws IOException { - Map params = getDefaultLoginParams(); - params.put("service", "ac2dm"); - params.put("add_account", "1"); - params.put("app", "com.google.android.gsf"); - byte[] responseBytes = getClient().post(URL_LOGIN, params, getDefaultHeaders()); - Map response = parseResponse(new String(responseBytes)); - if (response.containsKey("Auth")) { - return response.get("Auth"); - } else { - throw new GooglePlayException("Authentication failed! (loginAC2DM)"); - } - } - - public Map c2dmRegister(String application, String sender) throws IOException { - Map params = new HashMap<>(); - params.put("app", application); - params.put("sender", sender); - params.put("device", new BigInteger(this.getGsfId(), 16).toString()); - Map headers = getDefaultHeaders(); - headers.put("Authorization", "GoogleLogin auth=" + getAC2DMToken()); - byte[] responseBytes = getClient().post(C2DM_REGISTER_URL, params, headers); - return parseResponse(new String(responseBytes)); - } - - /** - * Equivalent of search(query, null, null) - */ - public SearchResponse search(String query) throws IOException { - return search(query, null, null); - } - - /** - * Fetches a search results for given query. Offset and numberOfResults - * parameters are optional and null can be passed! - * - * Warning! offset and numberOfResults do not seem to work anymore. - * The api always returns first 30 results. Fetching further results is done through - * nextPageUrl returned with the search result. - */ - public SearchResponse search(String query, Integer offset, Integer numberOfResults) throws IOException { - String url = SEARCH_URL; - Map params = new HashMap<>(); - if (this.searchNextPage.containsKey(query)) { - url = this.searchNextPage.get(query); - if (null == url) { - throw new GooglePlayException("No more results for query " + query); - } - } else { - params = getDefaultGetParams(offset, numberOfResults); - params.put("q", query); - } - - byte[] content = getClient().get(url, params, getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - SearchResponse response = responseWrapper.getPayload().getSearchResponse(); - if (response.getDocCount() > 0 - && response.getDocList().get(0).hasContainerMetadata() - && response.getDocList().get(0).getContainerMetadata().hasNextPageUrl() - ) { - this.searchNextPage.put(query, FDFE_URL + response.getDocList().get(0).getContainerMetadata().getNextPageUrl()); - } else { - this.searchNextPage.put(query, null); - } - return responseWrapper.getPayload().getSearchResponse(); - } - - public boolean hasNextSearchPage(String query) { - return !this.searchNextPage.containsKey(query) || this.searchNextPage.get(query) != null; - } - - /** - * Fetches detailed information about passed package name. If it is needed - * to fetch information about more than one application, consider to use - * bulkDetails. - */ - public DetailsResponse details(String packageName) throws IOException { - Map params = new HashMap<>(); - params.put("doc", packageName); - - byte[] content = getClient().get(DETAILS_URL, params, getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - return responseWrapper.getPayload().getDetailsResponse(); - } - - /** - * Equivalent of details but bulky one! - */ - public BulkDetailsResponse bulkDetails(List packageNames) throws IOException { - BulkDetailsRequest.Builder bulkDetailsRequestBuilder = BulkDetailsRequest.newBuilder(); - bulkDetailsRequestBuilder.addAllDocid(packageNames); - byte[] request = bulkDetailsRequestBuilder.build().toByteArray(); - - byte[] content = getClient().post(BULKDETAILS_URL, request, getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - return responseWrapper.getPayload().getBulkDetailsResponse(); - } - - /** - * Fetches available categories - */ - public BrowseResponse browse() throws IOException { - return browse(null, null); - } - - public BrowseResponse browse(String categoryId, String subCategoryId) throws IOException { - Map params = getDefaultGetParams(null, null); - params.put("cat", categoryId); - params.put("ctr", subCategoryId); - byte[] content = getClient().get(BROWSE_URL, params, getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - return responseWrapper.getPayload().getBrowseResponse(); - } - - /** - * Equivalent of list(categoryId, null, null, null). It fetches - * sub-categories of given category! - */ - public ListResponse list(String categoryId) throws IOException { - return list(categoryId, null, null, null); - } - - /** - * Fetches applications within supplied category and sub-category. If - * null is given for sub-category, it fetches sub-categories of - * passed category. - *

- * Default values for offset and numberOfResult are "0" and "20" - * respectively. These values are determined by Google Play Store. - */ - public ListResponse list(String categoryId, String subCategoryId, Integer offset, Integer numberOfResults) throws IOException { - Map params = getDefaultGetParams(offset, numberOfResults); - params.put("cat", categoryId); - params.put("ctr", subCategoryId); - byte[] content = getClient().get(LIST_URL, params, getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - return responseWrapper.getPayload().getListResponse(); - } - - /** - * This function is used for fetching download url and download cookie, - * rather than actual purchasing. - */ - public BuyResponse purchase(String packageName, int versionCode, int offerType) throws IOException { - Map params = new HashMap<>(); - params.put("ot", String.valueOf(offerType)); - params.put("doc", packageName); - params.put("vc", String.valueOf(versionCode)); - byte[] content = getClient().post(PURCHASE_URL, params, getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - return responseWrapper.getPayload().getBuyResponse(); - } - - /** - * Fetches the reviews of given package name by sorting passed choice. - *

- * Default values for offset and numberOfResult are "0" and "20" - * respectively. These values are determined by Google Play Store. - */ - public ReviewResponse reviews(String packageName, REVIEW_SORT sort, Integer offset, Integer numberOfResults) throws IOException { - Map params = getDefaultGetParams(offset, numberOfResults); - params.put("doc", packageName); - params.put("sort", (sort == null) ? null : String.valueOf(sort.value)); - byte[] content = getClient().get(REVIEWS_URL, params, getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - return responseWrapper.getPayload().getReviewResponse(); - } - - /** - * Uploads device configuration to google server so that can be seen from - * web as a registered device!! - */ - public UploadDeviceConfigResponse uploadDeviceConfig() throws IOException { - UploadDeviceConfigRequest request = UploadDeviceConfigRequest.newBuilder() - .setDeviceConfiguration(this.deviceInfoProvider.getDeviceConfigurationProto()) - .build(); - byte[] content = getClient().post(UPLOADDEVICECONFIG_URL, request.toByteArray(), getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - return responseWrapper.getPayload().getUploadDeviceConfigResponse(); - } - - /** - * Fetches the recommendations of given package name. - *

- * Default values for offset and numberOfResult are "0" and "20" - * respectively. These values are determined by Google Play Store. - */ - public ListResponse recommendations(String packageName, RECOMMENDATION_TYPE type, Integer offset, Integer numberOfResults) throws IOException { - Map params = getDefaultGetParams(offset, numberOfResults); - params.put("doc", packageName); - params.put("rt", (type == null) ? null : String.valueOf(type.value)); - byte[] content = getClient().get(RECOMMENDATIONS_URL, params, getDefaultHeaders()); - ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content); - return responseWrapper.getPayload().getListResponse(); - } - - /** - * login methods use this - * Most likely not all of these are required, but the Market app sends them, so we will too - * - */ - private Map getDefaultLoginParams() { - Map params = new HashMap<>(); - params.put("Email", this.email); - params.put("Passwd", this.password); - params.put("accountType", ACCOUNT_TYPE_HOSTED_OR_GOOGLE); - params.put("has_permission", "1"); - params.put("source", "android"); - params.put("device_country", this.locale.getCountry().toLowerCase()); - params.put("lang", this.locale.getLanguage().toLowerCase()); - params.put("sdk_version", String.valueOf(this.deviceInfoProvider.getSdkVersion())); - params.put("client_sig", "38918a453d07199354f8b19af05ec6562ced5788"); - return params; - } - - /** - * Using Accept-Language you can fetch localized informations such as reviews and descriptions. - * Note that changing this value has no affect on localized application list that - * server provides. It depends on only your IP location. - * - */ - private Map getDefaultHeaders() { - Map headers = new HashMap<>(); - if (this.token != null && !this.token.isEmpty()) { - headers.put("Authorization", "GoogleLogin auth=" + this.token); - } - headers.put("User-Agent", this.deviceInfoProvider.getUserAgentString()); - if (this.gsfId != null && !this.gsfId.isEmpty()) { - headers.put("X-DFE-Device-Id", this.gsfId); - } - headers.put("Accept-Language", this.locale.toString().replace("_", "-")); - return headers; - } - - /** - * Most list requests (apps, categories,..) take these params - * - * @param offset - * @param numberOfResults - */ - private Map getDefaultGetParams(Integer offset, Integer numberOfResults) { - Map params = new HashMap<>(); - params.put("c", "3"); - if (offset != null) { - params.put("o", String.valueOf(offset)); - } - if (numberOfResults != null) { - params.put("n", String.valueOf(numberOfResults)); - } - return params; - } - - private static Map parseResponse(String response) { - Map keyValueMap = new HashMap<>(); - StringTokenizer st = new StringTokenizer(response, "\n\r"); - while (st.hasMoreTokens()) { - String[] keyValue = st.nextToken().split("="); - keyValueMap.put(keyValue[0], keyValue[1]); - } - return keyValueMap; - } -} diff --git a/app/src/main/java/com/github/yeriomin/playstoreapi/GooglePlayException.java b/app/src/main/java/com/github/yeriomin/playstoreapi/GooglePlayException.java deleted file mode 100644 index e45b77835..000000000 --- a/app/src/main/java/com/github/yeriomin/playstoreapi/GooglePlayException.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.yeriomin.playstoreapi; - -import java.io.IOException; - -public class GooglePlayException extends IOException { - - private int code; - - public GooglePlayException(String message) { - super(message); - } - public GooglePlayException(String message, int code) { - super(message); - this.code = code; - } - - public GooglePlayException(String message, Throwable cause) { - super(message, cause); - } - - public int getCode() { - return this.code; - } -} diff --git a/app/src/main/java/com/github/yeriomin/playstoreapi/PropertiesDeviceInfoProvider.java b/app/src/main/java/com/github/yeriomin/playstoreapi/PropertiesDeviceInfoProvider.java deleted file mode 100644 index b288b4661..000000000 --- a/app/src/main/java/com/github/yeriomin/playstoreapi/PropertiesDeviceInfoProvider.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.github.yeriomin.playstoreapi; - -import java.util.Arrays; -import java.util.Properties; - -public class PropertiesDeviceInfoProvider implements DeviceInfoProvider { - - private Properties properties; - private String localeString; - - public void setProperties(Properties properties) { - this.properties = properties; - } - - public void setLocaleString(String localeString) { - this.localeString = localeString; - } - - public int getSdkVersion() { - return Integer.parseInt(this.properties.getProperty("Build.VERSION.SDK_INT")); - } - - public String getUserAgentString() { - return "Android-Finsky/7.1.15 (" - + "api=3" - + ",versionCode=80711500" - + ",sdk=" + this.properties.getProperty("Build.VERSION.SDK_INT") - + ",device=" + this.properties.getProperty("Build.DEVICE") - + ",hardware=" + this.properties.getProperty("Build.HARDWARE") - + ",product=" + this.properties.getProperty("Build.PRODUCT") - + ")"; - } - - public AndroidCheckinRequest generateAndroidCheckinRequest() { - return AndroidCheckinRequest.newBuilder() - .setId(0) - .setCheckin( - AndroidCheckinProto.newBuilder() - .setBuild( - AndroidBuildProto.newBuilder() - .setId(this.properties.getProperty("Build.FINGERPRINT")) - .setProduct(this.properties.getProperty("Build.HARDWARE")) - .setCarrier(this.properties.getProperty("Build.BRAND")) - .setRadio(this.properties.getProperty("Build.RADIO")) - .setBootloader(this.properties.getProperty("Build.BOOTLOADER")) - .setDevice(this.properties.getProperty("Build.DEVICE")) - .setSdkVersion(Integer.getInteger(this.properties.getProperty("Build.VERSION.SDK_INT"))) - .setModel(this.properties.getProperty("Build.MODEL")) - .setManufacturer(this.properties.getProperty("Build.MANUFACTURER")) - .setBuildProduct(this.properties.getProperty("Build.PRODUCT")) - .setClient(this.properties.getProperty("Client")) - .setOtaInstalled(Boolean.getBoolean(this.properties.getProperty("OtaInstalled"))) - .setTimestamp(System.currentTimeMillis() / 1000) - .setGoogleServices(Integer.getInteger(this.properties.getProperty("GSF.version"))) - ) - .setLastCheckinMsec(0) - .setCellOperator(this.properties.getProperty("CellOperator")) - .setSimOperator(this.properties.getProperty("SimOperator")) - .setRoaming(this.properties.getProperty("Roaming")) - .setUserNumber(0) - ) - .setLocale(this.localeString) - .setTimeZone(this.properties.getProperty("TimeZone")) - .setVersion(3) - .setDeviceConfiguration(getDeviceConfigurationProto()) - .setFragment(0) - .build(); - } - - public DeviceConfigurationProto getDeviceConfigurationProto() { - return DeviceConfigurationProto.newBuilder() - .setTouchScreen(Integer.getInteger(this.properties.getProperty("TouchScreen"))) - .setKeyboard(Integer.getInteger(this.properties.getProperty("Keyboard"))) - .setNavigation(Integer.getInteger(this.properties.getProperty("Navigation"))) - .setScreenLayout(Integer.getInteger(this.properties.getProperty("ScreenLayout"))) - .setHasHardKeyboard(Boolean.getBoolean(this.properties.getProperty("HasHardKeyboard"))) - .setHasFiveWayNavigation(Boolean.getBoolean(this.properties.getProperty("HasFiveWayNavigation"))) - .setScreenDensity(Integer.getInteger(this.properties.getProperty("Screen.Density"))) - .setScreenWidth(Integer.getInteger(this.properties.getProperty("Screen.Width"))) - .setScreenHeight(Integer.getInteger(this.properties.getProperty("Screen.Height"))) - .addAllNativePlatform(Arrays.asList(this.properties.getProperty("Platforms").split(","))) - .addAllSystemSharedLibrary(Arrays.asList(this.properties.getProperty("SharedLibraries").split(","))) - .addAllSystemAvailableFeature(Arrays.asList(this.properties.getProperty("Features").split(","))) - .addAllSystemSupportedLocale(Arrays.asList(this.properties.getProperty("Locales").split(","))) - .setGlEsVersion(Integer.getInteger(this.properties.getProperty("GL.Version"))) - .addAllGlExtension(Arrays.asList(this.properties.getProperty("GL.Extensions").split(","))) - .build(); - } -} diff --git a/app/src/main/java/com/github/yeriomin/playstoreapi/ThrottledOkHttpClient.java b/app/src/main/java/com/github/yeriomin/playstoreapi/ThrottledOkHttpClient.java deleted file mode 100644 index c450d4c80..000000000 --- a/app/src/main/java/com/github/yeriomin/playstoreapi/ThrottledOkHttpClient.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.github.yeriomin.playstoreapi; - -import android.os.SystemClock; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import okhttp3.Cookie; -import okhttp3.CookieJar; -import okhttp3.FormBody; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -class ThrottledOkHttpClient { - - private static final long DEFAULT_REQUEST_INTERVAL = 2000; - - private long lastRequestTime; - private long requestInterval = DEFAULT_REQUEST_INTERVAL; - private OkHttpClient client; - - public void setRequestInterval(long requestInterval) { - this.requestInterval = requestInterval; - } - - public ThrottledOkHttpClient() { - this.client = new OkHttpClient.Builder() - .connectTimeout(3, TimeUnit.SECONDS) - .readTimeout(3, TimeUnit.SECONDS) - .cookieJar(new CookieJar() { - private final HashMap> cookieStore = new HashMap<>(); - - @Override - public void saveFromResponse(HttpUrl url, List cookies) { - cookieStore.put(url, cookies); - } - - @Override - public List loadForRequest(HttpUrl url) { - List cookies = cookieStore.get(url); - return cookies != null ? cookies : new ArrayList(); - } - }) - .build(); - } - - public byte[] get(String url, Map params) throws IOException { - return get(url, params, null); - } - - public byte[] get(String url, Map params, Map headers) throws IOException { - HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); - if (null != params && !params.isEmpty()) { - for (String name: params.keySet()) { - urlBuilder.addQueryParameter(name, params.get(name)); - } - } - - Request.Builder requestBuilder = new Request.Builder() - .url(urlBuilder.build()) - .get(); - - return request(requestBuilder, headers); - } - - public byte[] post(String url, Map params, Map headers) throws IOException { - headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); - - FormBody.Builder bodyBuilder = new FormBody.Builder(); - if (null != params && !params.isEmpty()) { - for (String name: params.keySet()) { - bodyBuilder.add(name, params.get(name)); - } - } - - Request.Builder requestBuilder = new Request.Builder() - .url(url) - .post(bodyBuilder.build()); - - return post(url, requestBuilder, headers); - } - - public byte[] post(String url, byte[] body, Map headers) throws IOException { - if (!headers.containsKey("Content-Type")) { - headers.put("Content-Type", "application/x-protobuf"); - } - - Request.Builder requestBuilder = new Request.Builder() - .url(url) - .post(RequestBody.create(MediaType.parse("application/x-protobuf"), body)); - - return post(url, requestBuilder, headers); - } - - private byte[] post(String url, Request.Builder requestBuilder, Map headers) throws IOException { - requestBuilder.url(url); - - return request(requestBuilder, headers); - } - - private byte[] request(Request.Builder requestBuilder, Map headers) throws IOException { - if (this.lastRequestTime > 0) { - long msecRemaining = this.requestInterval - System.currentTimeMillis() + this.lastRequestTime; - if (msecRemaining > 0) { - SystemClock.sleep(msecRemaining); - } - } - - Request request = requestBuilder - .headers(Headers.of(headers)) - .build(); - System.out.println("Requesting: " + request.url().toString()); - - Response response = client.newCall(request).execute(); - - int code = response.code(); - byte[] content = response.body().bytes(); - - if (code >= 400) { - throw new GooglePlayException(String.valueOf(code) + " Probably an auth error: " + new String(content), code); - } - - return content; - } -} diff --git a/app/src/main/java/com/github/yeriomin/playstoreapi/Utils.java b/app/src/main/java/com/github/yeriomin/playstoreapi/Utils.java deleted file mode 100644 index f82702030..000000000 --- a/app/src/main/java/com/github/yeriomin/playstoreapi/Utils.java +++ /dev/null @@ -1,281 +0,0 @@ -package com.github.yeriomin.playstoreapi; - -/** - * @author akdeniz - */ -public class Utils { - - /* -* - - id: "Xiaomi/gemini/gemini:6.0/MRA58K/V7.2.8.0.MAACNDB:user/release-keys" - product: "qcom" - carrier: "Xiaomi" - radio: "TH20.c1.3-0321_1155_57c5007" - bootloader: "unknown" - client: "unknown" - timestamp: 1458643019 - googleServices: 10084448 - device: "gemini" - sdkVersion: 23 - model: "MI 5" - manufacturer: "Xiaomi" - buildProduct: "gemini" - otaInstalled: 0 - - -Build.BOARD msm8996 -Build.BOOTLOADER unknown -Build.BRAND Xiaomi -Build.DEVICE gemini -Build.DISPLAY MRA58K -Build.FINGERPRINT Xiaomi/gemini/gemini:6.0/MRA58K/V7.2.8.0.MAACNDB:user/release-keys -Build.HARDWARE qcom -Build.HOST qh-miui-ota-bd72.bj -Build.ID MRA58K -Build.MANUFACTURER Xiaomi -Build.MODEL MI 5 -Build.PRODUCT gemini -Build.SERIAL d0189fa4 -Build.TAGS release-keys -Build.TYPE user -Build.UNKNOWN unknown -Build.USER builder -Build.CPU_ABI arm64-v8a -Build.CPU_ABI2 -Build.TIME 1458643019000 -Build.VERSION.CODENAME REL -Build.VERSION.INCREMENTAL V7.2.8.0.0.MAACNDB -Build.VERSION.RELEASE 6.0 -Build.VERSION.SDK_INT 23 - */ - -// /** -// * Generates android checkin request with properties of "Galaxy S3". -// *

-// * http://www.glbenchmark.com/phonedetails.jsp?benchmark=glpro25&D=Samsung -// * +GT-I9300+Galaxy+S+III&testgroup=system -// */ -// public static AndroidCheckinRequest generateAndroidCheckinRequest() { -// -// return AndroidCheckinRequest -// .newBuilder() -// .setId(0) -// .setCheckin( -// AndroidCheckinProto.newBuilder() -// .setBuild( -// AndroidBuildProto.newBuilder() -// .setId(Build.FINGERPRINT) -// .setProduct(Build.HARDWARE) -// .setCarrier(Build.BRAND) -// .setRadio(Build.RADIO) -// .setBootloader(Build.BOOTLOADER) -// .setDevice(Build.DEVICE) -// .setSdkVersion(Build.VERSION.SDK_INT) -// .setModel(Build.MODEL) -// .setManufacturer(Build.MANUFACTURER) -// .setBuildProduct(Build.PRODUCT) -// .setClient("android-google") -// .setOtaInstalled(false) -// .setTimestamp(new Date().getTime() / 1000) -// .setGoogleServices(16) -// ) -// .setLastCheckinMsec(0) -// .setCellOperator("310260") -// .setSimOperator("310260") -// .setRoaming("mobile-notroaming") -// .setUserNumber(0) -// ) -// .setLocale(Locale.getDefault().toString()) -// .setTimeZone(TimeZone.getDefault().getID()) -// .setVersion(3) -// .setDeviceConfiguration(getDeviceConfigurationProto()) -// .setFragment(0) -// .build(); -// } -// -// public static AndroidCheckinRequest generateAndroidCheckinRequestOriginal() { -// -// return AndroidCheckinRequest -// .newBuilder() -// .setId(0) -// .setCheckin( -// AndroidCheckinProto.newBuilder() -// .setBuild( -// AndroidBuildProto.newBuilder() -// .setId("samsung/m0xx/m0:4.0.4/IMM76D/I9300XXALF2:user/release-keys") -// .setProduct("smdk4x12") -// .setCarrier("Google") -// .setRadio("I9300XXALF2") -// .setBootloader("PRIMELA03") -// .setClient("android-google") -// .setTimestamp(new Date().getTime() / 1000) -// .setGoogleServices(16) -// .setDevice("m0") -// .setSdkVersion(16) -// .setModel("GT-I9300") -// .setManufacturer("Samsung") -// .setBuildProduct("m0xx") -// .setOtaInstalled(false) -// ) -// .setLastCheckinMsec(0) -// .setCellOperator("310260") -// .setSimOperator("310260") -// .setRoaming("mobile-notroaming") -// .setUserNumber(0) -// ) -// .setLocale("en_US") -// .setTimeZone("Europe/Istanbul") -// .setVersion(3) -// .setDeviceConfiguration(getDeviceConfigurationProto()) -// .setFragment(0) -// .build(); -// } -// -// public static AndroidCheckinRequest generateAndroidCheckinRequestNviennot() { -// -// return AndroidCheckinRequest -// .newBuilder() -// .setId(0) -// .setCheckin( -// AndroidCheckinProto.newBuilder() -// .setBuild( -// AndroidBuildProto.newBuilder() -// .setId("google/yakju/maguro:4.1.1/JRO03C/398337:user/release-keys") -// .setProduct("tuna") -// .setCarrier("Google") -// .setRadio("I9250XXLA2") -// .setBootloader("PRIMELA03") -// .setClient("android-google") -// .setTimestamp(new Date().getTime()/1000) -// .setGoogleServices(16) -// .setDevice("maguro") -// .setSdkVersion(16) -// .setModel("Galaxy Nexus") -// .setManufacturer("Samsung") -// .setBuildProduct("yakju") -// .setOtaInstalled(false) -// ) -// .setLastCheckinMsec(0) -// .setCellOperator("310260") -// .setSimOperator("310260") -// .setRoaming("mobile-notroaming") -// .setUserNumber(0) -// ) -// .setLocale("en_US") -// .setTimeZone("Europe/Istanbul") -// .setVersion(3) -// .setDeviceConfiguration(getDeviceConfigurationProto()) -// .setFragment(0) -// .build(); -// } -// -// public static AndroidCheckinRequest generateAndroidCheckinRequestRacoon() { -// -// return AndroidCheckinRequest -// .newBuilder() -// .setId(0) -// .setCheckin( -// AndroidCheckinProto.newBuilder() -// .setBuild( -// AndroidBuildProto.newBuilder() -// .setId("samsung/nobleltejv/noblelte:6.0.1/MMB29K/N920CXXU2BPD6:user/release-keys") -// .setProduct("noblelte") -// .setCarrier("Google") -// .setRadio("I9300XXALF2") -// .setBootloader("PRIMELA03") -// .setClient("android-google") -// .setTimestamp(new Date().getTime() / 1000) -// .setGoogleServices(16) -// .setDevice("noblelte") -// .setSdkVersion(23) -// .setModel("SM-N920C") -// .setManufacturer("Samsung") -// .setBuildProduct("noblelte") -// .setOtaInstalled(false) -// ) -// .setLastCheckinMsec(0) -// .setCellOperator("310260") -// .setSimOperator("310260") -// .setRoaming("mobile-notroaming") -// .setUserNumber(0) -// ) -// .setLocale("en_US") -// .setTimeZone("Europe/Berlin") -// .setVersion(3) -// .setDeviceConfiguration(getDeviceConfigurationProto()) -// .setFragment(0) -// .build(); -// } -// -// public static DeviceConfigurationProto getDeviceConfigurationProtoAAAAA() { -//// DisplayMetrics metrics = new DisplayMetrics(); -//// WindowManager wm = (WindowManager) this.context.getSystemService(Context.WINDOW_SERVICE); -//// wm.getDefaultDisplay().getMetrics(metrics); -// return DeviceConfigurationProto.newBuilder() -// .setTouchScreen(3) -// .setKeyboard(1) -// .setNavigation(1) -// .setScreenLayout(2) -// .setHasHardKeyboard(false) -// .setHasFiveWayNavigation(false) -// .setScreenDensity(320) -// .setScreenWidth(720) -// .setScreenHeight(1184) -// .setGlEsVersion(131072) -// .addAllNativePlatform(Arrays.asList(Build.CPU_ABI, Build.CPU_ABI2)) -//// .addAllNativePlatform(Arrays.asList("armeabi-v7a", "armeabi")) -// .addAllSystemSharedLibrary( -// Arrays.asList("android.test.runner", "com.android.future.usb.accessory", "com.android.location.provider", -// "com.android.nfc_extras", "com.google.android.maps", "com.google.android.media.effects", -// "com.google.widevine.software.drm", "javax.obex")) -// .addAllSystemAvailableFeature( -// Arrays.asList("android.hardware.bluetooth", "android.hardware.camera", -// "android.hardware.camera.autofocus", "android.hardware.camera.flash", -// "android.hardware.camera.front", "android.hardware.faketouch", "android.hardware.location", -// "android.hardware.location.gps", "android.hardware.location.network", -// "android.hardware.microphone", "android.hardware.nfc", "android.hardware.screen.landscape", -// "android.hardware.screen.portrait", "android.hardware.sensor.accelerometer", -// "android.hardware.sensor.barometer", "android.hardware.sensor.compass", -// "android.hardware.sensor.gyroscope", "android.hardware.sensor.light", -// "android.hardware.sensor.proximity", "android.hardware.telephony", -// "android.hardware.telephony.gsm", "android.hardware.touchscreen", -// "android.hardware.touchscreen.multitouch", "android.hardware.touchscreen.multitouch.distinct", -// "android.hardware.touchscreen.multitouch.jazzhand", "android.hardware.usb.accessory", -// "android.hardware.usb.host", "android.hardware.wifi", "android.hardware.wifi.direct", -// "android.software.live_wallpaper", "android.software.sip", "android.software.sip.voip", -// "com.cyanogenmod.android", "com.cyanogenmod.nfc.enhanced", -// "com.google.android.feature.GOOGLE_BUILD", "com.nxp.mifare", "com.tmobile.software.themes")) -// .addAllSystemSupportedLocale( -// Arrays.asList("af", "af_ZA", "am", "am_ET", "ar", "ar_EG", "bg", "bg_BG", "ca", "ca_ES", "cs", "cs_CZ", -// "da", "da_DK", "de", "de_AT", "de_CH", "de_DE", "de_LI", "el", "el_GR", "en", "en_AU", "en_CA", -// "en_GB", "en_NZ", "en_SG", "en_US", "es", "es_ES", "es_US", "fa", "fa_IR", "fi", "fi_FI", "fr", -// "fr_BE", "fr_CA", "fr_CH", "fr_FR", "hi", "hi_IN", "hr", "hr_HR", "hu", "hu_HU", "in", "in_ID", -// "it", "it_CH", "it_IT", "iw", "iw_IL", "ja", "ja_JP", "ko", "ko_KR", "lt", "lt_LT", "lv", -// "lv_LV", "ms", "ms_MY", "nb", "nb_NO", "nl", "nl_BE", "nl_NL", "pl", "pl_PL", "pt", "pt_BR", -// "pt_PT", "rm", "rm_CH", "ro", "ro_RO", "ru", "ru_RU", "sk", "sk_SK", "sl", "sl_SI", "sr", -// "sr_RS", "sv", "sv_SE", "sw", "sw_TZ", "th", "th_TH", "tl", "tl_PH", "tr", "tr_TR", "ug", -// "ug_CN", "uk", "uk_UA", "vi", "vi_VN", "zh_CN", "zh_TW", "zu", "zu_ZA")) -// .addAllGlExtension( -// Arrays.asList("GL_EXT_debug_marker", "GL_EXT_discard_framebuffer", "GL_EXT_multi_draw_arrays", -// "GL_EXT_shader_texture_lod", "GL_EXT_texture_format_BGRA8888", -// "GL_IMG_multisampled_render_to_texture", "GL_IMG_program_binary", "GL_IMG_read_format", -// "GL_IMG_shader_binary", "GL_IMG_texture_compression_pvrtc", "GL_IMG_texture_format_BGRA8888", -// "GL_IMG_texture_npot", "GL_IMG_vertex_array_object", "GL_OES_EGL_image", -// "GL_OES_EGL_image_external", "GL_OES_blend_equation_separate", "GL_OES_blend_func_separate", -// "GL_OES_blend_subtract", "GL_OES_byte_coordinates", "GL_OES_compressed_ETC1_RGB8_texture", -// "GL_OES_compressed_paletted_texture", "GL_OES_depth24", "GL_OES_depth_texture", -// "GL_OES_draw_texture", "GL_OES_egl_sync", "GL_OES_element_index_uint", -// "GL_OES_extended_matrix_palette", "GL_OES_fixed_point", "GL_OES_fragment_precision_high", -// "GL_OES_framebuffer_object", "GL_OES_get_program_binary", "GL_OES_mapbuffer", -// "GL_OES_matrix_get", "GL_OES_matrix_palette", "GL_OES_packed_depth_stencil", -// "GL_OES_point_size_array", "GL_OES_point_sprite", "GL_OES_query_matrix", "GL_OES_read_format", -// "GL_OES_required_internalformat", "GL_OES_rgb8_rgba8", "GL_OES_single_precision", -// "GL_OES_standard_derivatives", "GL_OES_stencil8", "GL_OES_stencil_wrap", -// "GL_OES_texture_cube_map", "GL_OES_texture_env_crossbar", "GL_OES_texture_float", -// "GL_OES_texture_half_float", "GL_OES_texture_mirrored_repeat", "GL_OES_vertex_array_object", -// "GL_OES_vertex_half_float")).build(); -// } -} diff --git a/build.gradle b/build.gradle index 14c47cf07..e0de0c261 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ buildscript { allprojects { repositories { + maven { url 'https://jitpack.io' } jcenter() } }