// // Copyright (c) 2014 VK.com // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // package com.vk.sdk.api; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.util.Log; import com.vk.sdk.VKAccessToken; import com.vk.sdk.VKObject; import com.vk.sdk.VKOpenAuthActivity; import com.vk.sdk.VKSdk; import com.vk.sdk.VKSdkVersion; import com.vk.sdk.VKUIHelper; import com.vk.sdk.api.httpClient.VKAbstractOperation; import com.vk.sdk.api.httpClient.VKHttpClient; import com.vk.sdk.api.httpClient.VKHttpOperation; import com.vk.sdk.api.httpClient.VKJsonOperation; import com.vk.sdk.api.httpClient.VKJsonOperation.VKJSONOperationCompleteListener; import com.vk.sdk.api.httpClient.VKModelOperation; import com.vk.sdk.api.model.VKApiModel; import com.vk.sdk.util.VKStringJoiner; import com.vk.sdk.util.VKUtil; import org.apache.http.client.methods.HttpUriRequest; import org.json.JSONException; import org.json.JSONObject; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; /** * Class for execution API-requests. */ public class VKRequest extends VKObject { public enum VKProgressType { Download, Upload } public enum HttpMethod { GET, POST } /** * Selected method name */ public final String methodName; /** * HTTP method for loading */ public final HttpMethod httpMethod; /** * Passed parameters for method */ private final VKParameters mMethodParameters; /** * Method parametes with common parameters */ private VKParameters mPreparedParameters; /** * HTTP loading operation */ private VKAbstractOperation mLoadingOperation; /** * How much times request was loaded */ private int mAttemptsUsed; /** * Requests that should be called after current request. */ private ArrayList<VKRequest> mPostRequestsQueue; /** * Class for model parsing */ private Class<? extends VKApiModel> mModelClass; /** * Response parser */ private VKParser mModelParser; /** * Specify language for API request */ private String mPreferredLang; /** * Looper which starts request */ private Looper mLooper; /** * Specify listener for current request */ public VKRequestListener requestListener; /** * Specify attempts for request loading if caused HTTP-error. 0 for infinite */ public int attempts; /** * Use HTTPS requests (by default is YES). If http-request is impossible (user denied no https access), SDK will load https version */ public boolean secure; /** * Sets current system language as default for API data */ public boolean useSystemLanguage; /** * Set to false if you don't need automatic model parsing */ public boolean parseModel; /** * Response for this request */ public WeakReference<VKResponse> response; /** * @return Returns HTTP-method for current request */ public HttpMethod getHttpMethod() { return httpMethod; } /** * @return Returns list of method parameters (without common parameters) */ public VKParameters getMethodParameters() { return mMethodParameters; } /** * Creates new request with parameters. See documentation for methods here https://vk.com/dev/methods * * @param method API-method name, e.g. audio.get */ public VKRequest(String method) { this(method, null); } /** * Creates new request with parameters. See documentation for methods here https://vk.com/dev/methods * * @param method API-method name, e.g. audio.get * @param parameters method parameters */ public VKRequest(String method, VKParameters parameters) { this(method, parameters, HttpMethod.GET); } /** * Creates new request with parameters. See documentation for methods here https://vk.com/dev/methods * * @param method API-method name, e.g. audio.get * @param parameters method parameters * @param httpMethod HTTP method for execution, e.g. GET, POST */ public VKRequest(String method, VKParameters parameters, HttpMethod httpMethod) { this.methodName = method; if (parameters == null) { parameters = new VKParameters(); } this.mMethodParameters = new VKParameters(parameters); if (httpMethod == null) httpMethod = HttpMethod.GET; this.httpMethod = httpMethod; this.mAttemptsUsed = 0; this.secure = true; //By default there is 1 attempt for loading. this.attempts = 1; //If system language is not supported, we use english this.mPreferredLang = "en"; //By default we use system language. this.useSystemLanguage = true; } /** * Creates new request with parameters. See documentation for methods here https://vk.com/dev/methods * * @param method API-method name, e.g. audio.get * @param parameters method parameters * @param httpMethod HTTP method for execution, e.g. GET, POST * @param modelClass class for automatic parse */ public VKRequest(String method, VKParameters parameters, HttpMethod httpMethod, Class<? extends VKApiModel> modelClass) { this(method, parameters, httpMethod); setModelClass(modelClass); } /** * Executes that request, and returns result to blocks * * @param listener listener for request events */ public void executeWithListener(VKRequestListener listener) { this.requestListener = listener; start(); } public void setRequestListener(VKRequestListener listener) { this.requestListener = listener; } /** * Register current request for execute after passed request, if passed request is successful. If it's not, errorBlock will be called. * * @param request after which request must be called that request * @param listener listener for request events */ public void executeAfterRequest(VKRequest request, VKRequestListener listener) { this.requestListener = listener; request.addPostRequest(this); } private void addPostRequest(VKRequest postRequest) { if (mPostRequestsQueue == null) { mPostRequestsQueue = new ArrayList<VKRequest>(); } mPostRequestsQueue.add(postRequest); } public VKParameters getPreparedParameters() { if (mPreparedParameters == null) { mPreparedParameters = new VKParameters(mMethodParameters); //Set current access token from SDK object VKAccessToken token = VKSdk.getAccessToken(); if (token != null) mPreparedParameters.put(VKApiConst.ACCESS_TOKEN, token.accessToken); if (!this.secure) if (token != null && (token.secret != null || token.httpsRequired)) { this.secure = true; } //Set actual version of API mPreparedParameters.put(VKApiConst.VERSION, VKSdkVersion.API_VERSION); //Set preferred language for request mPreparedParameters.put(VKApiConst.LANG, getLang()); if (this.secure) { //If request is secure, we need all urls as https mPreparedParameters.put(VKApiConst.HTTPS, "1"); } if (token != null && token.secret != null) { //If it not, generate signature of request String sig = generateSig(token); mPreparedParameters.put(VKApiConst.SIG, sig); } //From that moment you cannot modify parameters. //Specially for http loading } return mPreparedParameters; } /** * Prepares request for loading * * @return Prepared HttpUriRequest for that VKRequest */ public HttpUriRequest getPreparedRequest() { HttpUriRequest request = VKHttpClient.requestWithVkRequest(this); if (request == null) { VKError error = new VKError(VKError.VK_REQUEST_NOT_PREPARED); provideError(error); return null; } return request; } public VKAbstractOperation getOperation() { if (this.parseModel) { if (this.mModelClass != null) { mLoadingOperation = new VKModelOperation(getPreparedRequest(), this.mModelClass); } else if (this.mModelParser != null){ mLoadingOperation = new VKModelOperation(getPreparedRequest(), this.mModelParser); } } if (mLoadingOperation == null) mLoadingOperation = new VKJsonOperation(getPreparedRequest()); ((VKJsonOperation) mLoadingOperation).setJsonOperationListener( new VKJSONOperationCompleteListener() { @Override public void onComplete(VKJsonOperation operation, JSONObject response) { if (response.has("error")) { try { VKError error = new VKError(response.getJSONObject("error")); if (VKSdk.DEBUG && VKSdk.DEBUG_API_ERRORS) { Log.w(VKSdk.SDK_TAG, operation.getResponseString()); } if (processCommonError(error)) return; provideError(error); } catch (JSONException e) { if (VKSdk.DEBUG) e.printStackTrace(); } return; } provideResponse(response, mLoadingOperation instanceof VKModelOperation ? ((VKModelOperation) mLoadingOperation).parsedModel : null); } @Override public void onError(VKJsonOperation operation, VKError error) { //Хак для проверки того, что корректно распарсился ответ при заливке картинок if ( error.errorCode != VKError.VK_CANCELED && error.errorCode != VKError.VK_API_ERROR && operation != null && operation.response != null && operation.response.getStatusLine().getStatusCode() == 200) { provideResponse(operation.getResponseJson(), null); return; } if (VKSdk.DEBUG && VKSdk.DEBUG_API_ERRORS && operation != null && operation.getResponseString() != null) { Log.w(VKSdk.SDK_TAG, operation.getResponseString()); } if (attempts == 0 || ++mAttemptsUsed < attempts) { if (requestListener != null) requestListener.attemptFailed(VKRequest.this, mAttemptsUsed, attempts); VKAbstractOperation.postInMainQueueDelayed(new Runnable() { @Override public void run() { start(); } }); return; } provideError(error); } }); return mLoadingOperation; } /** * Starts loading of prepared request. You can use it instead of executeWithResultBlock */ public void start() { if ((mLoadingOperation = getOperation()) == null) { return; } mLooper = Looper.myLooper(); VKHttpClient.enqueueOperation(mLoadingOperation); } /** * Repeats this request with initial parameters and blocks. * Used attempts will be set to 0. */ public void repeat() { this.mAttemptsUsed = 0; this.mPreparedParameters = null; this.mLoadingOperation = null; start(); } /** * Cancel current request. Result will be not passed. errorBlock will be called with error code */ public void cancel() { if (mLoadingOperation != null) mLoadingOperation.cancel(); else provideError(new VKError(VKError.VK_CANCELED)); } /** * Method used for errors processing * * @param error error caused by this request */ private void provideError(final VKError error) { error.request = this; runOnLooper(new Runnable() { @Override public void run() { if (requestListener != null) { requestListener.onError(error); } if (mPostRequestsQueue != null && mPostRequestsQueue.size() > 0) { for (VKRequest postRequest : mPostRequestsQueue) if (postRequest.requestListener != null) postRequest.requestListener.onError(error); } } }); } /** * Method used for response processing * * @param jsonResponse response from API * @param parsedModel model parsed from json */ private void provideResponse(final JSONObject jsonResponse, Object parsedModel) { final VKResponse response = new VKResponse(); response.request = this; response.json = jsonResponse; response.parsedModel = parsedModel; this.response = new WeakReference<VKResponse>(response); if (mLoadingOperation instanceof VKHttpOperation) { response.responseString = ((VKHttpOperation)mLoadingOperation).getResponseString(); } runOnLooper(new Runnable() { @Override public void run() { if (mPostRequestsQueue != null && mPostRequestsQueue.size() > 0) { for (final VKRequest request : mPostRequestsQueue) { request.start(); } } if (requestListener != null){ requestListener.onComplete(response); } } }); } /** * Adds additional parameter to that request * * @param key parameter name * @param value parameter value */ public void addExtraParameter(String key, Object value) { mMethodParameters.put(key, value); } /** * Adds additional parameters to that request * * @param extraParameters parameters supposed to be added */ public void addExtraParameters(VKParameters extraParameters) { mMethodParameters.putAll(extraParameters); } private String generateSig(VKAccessToken token) { //Read description here https://vk.com/dev/api_nohttps //At first, we need key-value pairs in order of request String queryString = VKStringJoiner.joinParams(mPreparedParameters); //Then we generate "request string" /method/{METHOD_NAME}?{GET_PARAMS}{POST_PARAMS} queryString = String.format(Locale.US, "/method/%s?%s", methodName, queryString); return VKUtil.md5(queryString + token.secret); } private boolean processCommonError(final VKError error) { if (error.errorCode == VKError.VK_API_ERROR) { if (error.apiError.errorCode == 14) { error.apiError.request = this; this.mLoadingOperation = null; runOnLooper(new Runnable() { @Override public void run() { VKSdk.instance().sdkListener().onCaptchaError(error.apiError); } }); return true; } else if (error.apiError.errorCode == 16) { VKAccessToken token = VKSdk.getAccessToken(); token.httpsRequired = true; runOnLooper(new Runnable() { @Override public void run() { repeat(); } }); return true; } else if (error.apiError.errorCode == 17) { if (VKUIHelper.getTopActivity() != null) { runOnMainLooper(new Runnable() { @Override public void run() { Intent i = new Intent(VKUIHelper.getTopActivity(), VKOpenAuthActivity.class); i.putExtra(VKOpenAuthActivity.VK_EXTRA_VALIDATION_URL, error.apiError.redirectUri); i.putExtra(VKOpenAuthActivity.VK_EXTRA_VALIDATION_REQUEST, VKRequest.this.registerObject()); VKUIHelper.getTopActivity() .startActivityForResult(i, VKSdk.VK_SDK_REQUEST_CODE); } }); return true; } } } return false; } private String getLang() { String result = mPreferredLang; if (useSystemLanguage) { result = Locale.getDefault().getLanguage(); if (result.equals("uk")) { result = "ua"; } if (!Arrays.asList(new String[]{"ru", "en", "ua", "es", "fi", "de", "it"}) .contains(result)) { result = mPreferredLang; } } return result; } /** * Sets preferred language for api results. * @param lang Two letter language code. May be "ru", "en", "ua", "es", "fi", "de", "it" */ public void setPreferredLang(String lang) { useSystemLanguage = false; mPreferredLang = lang; } /** * Sets class for parse object model * @param modelClass Class extends VKApiModel */ public void setModelClass(Class<? extends VKApiModel> modelClass) { mModelClass = modelClass; if (mModelClass != null) parseModel = true; } public void setResponseParser(VKParser parser) { mModelParser = parser; if (mModelParser != null) parseModel = true; } private void runOnLooper(Runnable block) { if (mLooper == null) { mLooper = Looper.getMainLooper(); } new Handler(mLooper).post(block); } private void runOnMainLooper(Runnable block) { new Handler(Looper.getMainLooper()).post(block); } /** * Extend listeners for requests from that class * Created by Roman Truba on 02.12.13. * Copyright (c) 2013 VK. All rights reserved. */ public static abstract class VKRequestListener implements Serializable { /** * Called if there were no HTTP or API errors, returns execution result. * * @param response response from VKRequest */ public void onComplete(final VKResponse response) { } /** * Called when request has failed attempt, and ready to do next attempt * * @param request Failed request * @param attemptNumber Number of failed attempt, started from 1 * @param totalAttempts Total request attempts defined for request */ public void attemptFailed(VKRequest request, int attemptNumber, int totalAttempts) { } /** * Called immediately if there was API error, or after <b>attempts</b> tries if there was an HTTP error * * @param error error for VKRequest */ public void onError(final VKError error) { } /** * Specify progress for uploading or downloading. Useless for text requests (because gzip encoding bytesTotal will always return -1) * * @param progressType type of progress (upload or download) * @param bytesLoaded total bytes loaded * @param bytesTotal total bytes suppose to be loaded */ public void onProgress(VKRequest.VKProgressType progressType, long bytesLoaded, long bytesTotal) { } } public static VKRequest getRegisteredRequest(long requestId) { return (VKRequest) getRegisteredObject(requestId); } }