//
// 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;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import com.vk.sdk.api.VKError;
import com.vk.sdk.api.VKRequest;
import com.vk.sdk.util.VKStringJoiner;
import com.vk.sdk.util.VKUtil;
import java.net.BindException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Entry point of SDK. See example for using properly
*/
public class VKSdk {
public static final boolean DEBUG = false;
public static final boolean DEBUG_API_ERRORS = false;
public static final String SDK_TAG = "VK SDK";
/**
* Start SDK activity for result with that request code
*/
public static final int VK_SDK_REQUEST_CODE = 0xf228;
/**
* Instance of SDK
*/
private static volatile VKSdk sInstance;
private static final String VK_SDK_ACCESS_TOKEN_PREF_KEY = "VK_SDK_ACCESS_TOKEN_PLEASE_DONT_TOUCH";
/**
* Responder for global SDK events
*/
private VKSdkListener mListener;
/**
* Access token for API-requests
*/
private VKAccessToken mAccessToken;
/**
* App id for current application
*/
private String mCurrentAppId;
private VKSdk() {
}
Context getContext() {
return VKUIHelper.getApplicationContext();
}
private static void checkConditions() throws BindException {
if (sInstance == null) {
throw new BindException("VK Sdk not yet initialized");
}
if (sInstance.getContext() == null) {
throw new BindException("Context must not be null");
}
}
/**
* Returns instance of VK sdk. You should never use that directly
*/
public static VKSdk instance() {
return sInstance;
}
/**
* Initialize SDK with responder for global SDK events
*
* @param listener responder for global SDK events
* @param appId your application id (if you haven't, you can create standalone application here https://vk.com/editapp?act=create )
*/
public static void initialize(VKSdkListener listener, String appId) {
if (listener == null) {
throw new NullPointerException("VK SDK listener cannot be null");
}
if (appId == null) {
throw new NullPointerException("Application ID cannot be null");
}
// Double checked locking singleton, for thread safety VKSdk.initialize() calls
if (sInstance == null) {
synchronized (VKSdk.class) {
if (sInstance == null) {
sInstance = new VKSdk();
}
}
}
sInstance.mListener = listener;
sInstance.mCurrentAppId = appId;
}
/**
* Initialize SDK with responder for global SDK events and custom token key
* (e.g., saved from other source or for some test reasons)
*
* @param listener responder for global SDK events
* @param appId your application id (if you haven't, you can create standalone application here https://vk.com/editapp?act=create )
* @param token custom-created access token
*/
public static Activity topActivity;
public static void initialize(VKSdkListener listener, String appId, VKAccessToken token) {
initialize(listener, appId);
sInstance.mAccessToken = token;
sInstance.performTokenCheck(token, true);
topActivity=VKUIHelper.getTopActivity();
}
/**
* Starts authorization process. If VKapp is available in system, it will opens and requests access from user.
* Otherwise UIWebView with standard UINavigationBar will be opened for access request.
*
* @param scope array of permissions for your applications. All permissions you can
*/
public static void authorize(String... scope) {
authorize(scope, false, false);
}
/**
* Defines true VK application fingerprint
*/
private static final String VK_APP_FINGERPRINT = "48761EEF50EE53AFC4CC9C5F10E6BDE7F8F5B82F";
private static final String VK_APP_PACKAGE_ID = "com.vkontakte.android";
private static final String VK_APP_AUTH_ACTION = "com.vkontakte.android.action.SDK_AUTH";
/**
* Starts authorization process. If VKapp is available in system, it will opens and requests access from user.
* Otherwise UIWebView with standard UINavigationBar will be opened for access request.
*
* @param scope array of permissions for your applications. All permissions you can
* @param revoke if true, user will allow logout (to change user)
* @param forceOAuth sdk will use only oauth authorization, through uiwebview
*/
public static void authorize(String[] scope, boolean revoke, boolean forceOAuth) {
try {
checkConditions();
} catch (Exception e) {
if (VKSdk.DEBUG)
e.printStackTrace();
return;
}
if (scope == null) {
scope = new String[]{};
}
ArrayList<String> scopeList = new ArrayList<String>(Arrays.asList(scope));
if (!scopeList.contains(VKScope.OFFLINE)) {
scopeList.add(VKScope.OFFLINE);
}
final Intent intent;
// if (!forceOAuth
// && VKUtil.isAppInstalled(sInstance.getContext(), VK_APP_PACKAGE_ID)
// && VKUtil.isIntentAvailable(sInstance.getContext(), VK_APP_AUTH_ACTION)) {
// intent = new Intent(VK_APP_AUTH_ACTION, null);
// } else {
intent = new Intent(sInstance.getContext(), VKOpenAuthActivity.class);
// }
intent.putExtra(VKOpenAuthActivity.VK_EXTRA_API_VERSION, VKSdkVersion.API_VERSION);
intent.putExtra(VKOpenAuthActivity.VK_EXTRA_CLIENT_ID, Integer.parseInt(sInstance.mCurrentAppId));
if (revoke) {
intent.putExtra(VKOpenAuthActivity.VK_EXTRA_REVOKE, true);
}
intent.putExtra(VKOpenAuthActivity.VK_EXTRA_SCOPE, VKStringJoiner.join(scopeList, ","));
if (VKUIHelper.getTopActivity() != null) {
VKUIHelper.getTopActivity().startActivityForResult(intent, VK_SDK_REQUEST_CODE);
}
else{
topActivity.startActivityForResult(intent, VK_SDK_REQUEST_CODE);
}
}
/**
* Returns current VK SDK listener
*
* @return Current sdk listener
*/
public VKSdkListener sdkListener() {
return sInstance.mListener;
}
/**
* Sets current VK SDK listener
*
* @param newListener listener for SDK
*/
public void setSdkListener(VKSdkListener newListener) {
sInstance.mListener = newListener;
}
/**
* Pass data of onActivityResult() function here
*
* @param resultCode result code of activity result
* @param result intent passed by activity
* @return If SDK parsed activity result properly, returns true. You can return from onActivityResult(). Otherwise, returns false.
* @deprecated Use processActivityResult(int requestCode, int resultCode, Intent result) instead
*/
public static boolean processActivityResult(int resultCode, Intent result) {
return processActivityResult(VK_SDK_REQUEST_CODE, resultCode, result);
}
/**
* Pass data of onActivityResult() function here
*
* @param requestCode request code of activity
* @param resultCode result code of activity result
* @param result intent passed by activity
* @return If SDK parsed activity result properly, returns true. You can return from onActivityResult(). Otherwise, returns false.
*/
public static boolean processActivityResult(int requestCode, int resultCode, Intent result) {
if (requestCode != VK_SDK_REQUEST_CODE) return false;
if (result != null) {
if (resultCode == Activity.RESULT_CANCELED) {
//Пользователь отменил (нажал назад)
setAccessTokenError(new VKError(VKError.VK_CANCELED));
return true;
}
if (resultCode == Activity.RESULT_OK) {
//Получен токен
if (result.hasExtra(VKOpenAuthActivity.VK_EXTRA_TOKEN_DATA)) {
String tokenInfo = result.getStringExtra(VKOpenAuthActivity.VK_EXTRA_TOKEN_DATA);
Map<String, String> tokenParams = VKUtil.explodeQueryString(tokenInfo);
boolean renew = result.getBooleanExtra(VKOpenAuthActivity.VK_EXTRA_VALIDATION_URL, false);
if (checkAndSetToken(tokenParams, renew) == CheckTokenResult.Success) {
VKRequest validationRequest = VKRequest.getRegisteredRequest(result.getLongExtra(VKOpenAuthActivity.VK_EXTRA_VALIDATION_REQUEST, 0));
if (validationRequest != null) {
validationRequest.repeat();
}
}
} else if (result.getExtras() != null) {
//Что-то пришло от Гриши
Map<String, String> tokenParams = new HashMap<String, String>();
for (String key : result.getExtras().keySet()) {
tokenParams.put(key, String.valueOf(result.getExtras().get(key)));
}
return checkAndSetToken(tokenParams, false) != CheckTokenResult.None;
}
return true;
}
return false;
}
setAccessTokenError(new VKError(VKError.VK_CANCELED));
return true;
}
enum CheckTokenResult {
None,
Success,
Error
}
/**
* Check new access token and sets it as working token
*
* @param tokenParams params of token
* @param renew flag indicates token renewal
* @return true if access token was set, or error was provided
*/
private static CheckTokenResult checkAndSetToken(Map<String, String> tokenParams, boolean renew) {
VKAccessToken token = VKAccessToken.tokenFromParameters(tokenParams);
if (token == null || token.accessToken == null) {
if (tokenParams.containsKey(VKAccessToken.SUCCESS)) {
return CheckTokenResult.Success;
}
VKError error = new VKError(tokenParams);
if (error.errorMessage != null || error.errorReason != null) {
setAccessTokenError(error);
return CheckTokenResult.Error;
}
} else {
setAccessToken(token, renew);
return CheckTokenResult.Success;
}
return CheckTokenResult.None;
}
/**
* Set API token to passed
*
* @param token token must be used for API requests
* @param renew flag indicates token renewal
*/
public static void setAccessToken(VKAccessToken token, boolean renew) {
sInstance.mAccessToken = token;
if (sInstance.mListener != null) {
if (!renew) {
sInstance.mListener.onReceiveNewToken(token);
} else {
sInstance.mListener.onRenewAccessToken(token);
}
}
sInstance.mAccessToken.saveTokenToSharedPreferences(VKUIHelper.getApplicationContext(), VK_SDK_ACCESS_TOKEN_PREF_KEY);
}
/**
* Returns token for API requests
*
* @return Received access token or null, if user not yet authorized
*/
public static VKAccessToken getAccessToken() {
if (sInstance.mAccessToken != null) {
if (sInstance.mAccessToken.isExpired() && sInstance.mListener != null) {
sInstance.mListener.onTokenExpired(sInstance.mAccessToken);
}
return sInstance.mAccessToken;
}
return null;
}
/**
* Notify SDK that user denied login
*
* @param error description of error while authorizing user
*/
public static void setAccessTokenError(final VKError error) {
if (sInstance.mListener != null) {
sInstance.mListener.onAccessDenied(error);
}
}
private boolean performTokenCheck(VKAccessToken token, boolean isUserToken) {
if (token != null) {
if (token.isExpired()) {
mListener.onTokenExpired(token);
} else if (token.accessToken != null) {
if (isUserToken) mListener.onAcceptUserToken(token);
return true;
} else {
VKError error = new VKError(VKError.VK_CANCELED);
error.errorMessage = "User token is invalid";
mListener.onAccessDenied(error);
}
}
return false;
}
public static boolean wakeUpSession(Activity activity) {
VKAccessToken token = VKAccessToken.tokenFromSharedPreferences(VKUIHelper.getTopActivity(),
VK_SDK_ACCESS_TOKEN_PREF_KEY);
if (sInstance.performTokenCheck(token, false)) {
sInstance.mAccessToken = token;
return true;
}
return false;
}
public static void logout() {
CookieSyncManager.createInstance(VKUIHelper.getApplicationContext());
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
sInstance.mAccessToken = null;
VKAccessToken.removeTokenAtKey(VKUIHelper.getApplicationContext(), VK_SDK_ACCESS_TOKEN_PREF_KEY);
}
public static boolean isLoggedIn() {
return sInstance.mAccessToken != null && !sInstance.mAccessToken.isExpired();
}
}