Back to project page kakao-android-sdk-standalone.
The source code is released under:
Apache License
If you think the Android project kakao-android-sdk-standalone listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/** * Copyright 2014 Minyoung Jeong <kkungkkung@gmail.com> * Copyright 2014 Kakao Corp./*from w ww . java2s .c o m*/ * * Redistribution and modification in source or binary forms are not permitted without specific prior written permission. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.kakao; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import com.kakao.authorization.AuthorizationResult; import com.kakao.authorization.accesstoken.AccessToken; import com.kakao.authorization.accesstoken.AccessTokenRequest; import com.kakao.authorization.authcode.AuthorizationCode; import com.kakao.authorization.authcode.AuthorizationCodeRequest; import com.kakao.exception.KakaoException; import com.kakao.exception.KakaoException.ERROR_TYPE; import com.kakao.helper.Logger; import com.kakao.helper.SharedPreferencesCache; import com.kakao.helper.Utility; import java.util.ArrayList; import java.util.List; /** * ????? ???? ?? ???? ???? access token??? ????. * @author MJ */ public class Session { private static Session currentSession; private static final String APP_KEY_PROPERTY = "com.kakao.sdk.AppKey"; private static final String REDIRECT_URL_PREFIX = "kakao"; private static final String REDIRECT_URL_POSTFIX = "://oauth"; private final Context context; private final String appKey; private final String redirectUri; private final SharedPreferencesCache appCache; private final List<SessionCallback> sessionCallbacks; private final Handler sessionCallbackHandler; private final Object INSTANCE_LOCK = new Object(); // ?? ????? ?????? ??? INSTANCE_LOCK??? ??? ???. private SessionState state; // close? ?? private RequestType requestType; private AuthorizationCode authorizationCode; private AccessToken accessToken; /** * ????? ???? ??? ????? ?????, ???? ??????? ???????? ????? ??????. * ???? ????? ??? ? ????. * opened ??? : ???? acitivity? * closed ??? : ???? action??? ?? open ???? * opening ??? : ???? ?? ???? * @param context ????? ???? context. ??? ?? app key? redirect uri? ????. * @param sessionCallback ???? ????? ??? ? ????? ??? ?? ?? */ public static synchronized boolean initializeSession(final Context context, final SessionCallback sessionCallback) { if (currentSession == null) { currentSession = new Session(context); } return currentSession.implicitOpen(sessionCallback); } /** * ???? ????? ???? ??? ????. * ???? ????? background? ????? ??????? ????. * @param sessionCallback ????? ????????? ? ?????? ?? * @return ???? ????? ??? ?? true, ???? ????? ?? ???? false? return ??. */ public boolean implicitOpen(final SessionCallback sessionCallback) { if (currentSession.isOpening() && currentSession.accessToken.hasRefreshToken()) { open(sessionCallback); return true; } else { return false; } } /** * ?? ????? ????. * @return ?? ?? ??? */ public static synchronized Session getCurrentSession() { if(currentSession == null) throw new IllegalStateException("Session is not initialized. Use Session#initializeSession(Context ,SessionCallback) in login process."); return currentSession; } /** * ?? ????? ????. * {@link SessionState#OPENED} ??????? ?? ??. * {@link SessionState#CLOSED} ??????? authorization code ??. ???/??? {@link SessionState#CLOSED} * {@link SessionState#OPENING} ??????? code ??? refresh token ?????? access token ??? ????. ???/??? {@link SessionState#CLOSED}, refresh ?????? {@link SessionState#OPENING} ??. * param?? ???? ???? ? ??? ????. * @param sessionCallback ?? ??? ???? ?? */ public void open(final SessionCallback sessionCallback) { // ???? open??? ???? ??. if (getState().isOpened()) { return; } addCallback(sessionCallback); //????? ???? request? ??. if(getRequestType() != null){ Logger.getInstance().d(getRequestType() + " is still doing."); return; } try { checkLoginActivity(); synchronized (INSTANCE_LOCK){ switch (state) { case CLOSED: if (appKey != null && redirectUri != null) { this.requestType = RequestType.GETTING_AUTHORIZATION_CODE; final AuthorizationCodeRequest authorizationCodeRequest = AuthorizationCodeRequest.createNewRequest(appKey, redirectUri); requestLogin(getLoginActivityIntent(authorizationCodeRequest)); } else { internalClose(new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "can not request authorization code because appKey or redirectUri is invalid."), false); } break; case OPENING: if(accessToken.hasRefreshToken()){ this.requestType = RequestType.REFRESHING_ACCESS_TOKEN; final AccessTokenRequest accessTokenRequest = AccessTokenRequest.createRequestWithRefreshToken(context, appKey, redirectUri, accessToken.getRefreshTokenString()); requestLogin(getLoginActivityIntent(accessTokenRequest)); } else if(authorizationCode.hasAuthorizationCode()){ this.requestType = RequestType.GETTING_ACCESS_TOKEN; final AccessTokenRequest accessTokenRequest = AccessTokenRequest.createRequestWithAuthorizationCode(context, appKey, redirectUri, authorizationCode.getAuthorizationCode()); requestLogin(getLoginActivityIntent(accessTokenRequest)); } else { internalClose(new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "can not request access token because both authorization code and refresh token are invalid."), false); } break; default: throw new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "current session state is not possible to open. state = " + state); } } } catch (KakaoException e) { internalClose(e, false); } } /** * ???? ?? close(????/??). request? ??? ????? ??? ??? ?? ????. * token??? ???? ???? authorization code??(????? ??) ?? ??? ????? open ????. * @param sessionCallback close ??? ???? ?? callback */ public void close(final SessionCallback sessionCallback) { if(sessionCallback != null) addCallback(sessionCallback); internalClose(null, true); } /** * ?? ????? ??? ?? access token??? ????? ??? ????? ???? ????. * ???????? opened ???? ?? opening???? ??????. * @return ????? ??? */ public final SessionState checkState() { synchronized (INSTANCE_LOCK) { if(state.isOpened() && !accessToken.hasValidAccessToken()){ synchronized (INSTANCE_LOCK) { state = SessionState.OPENING; requestType = null; authorizationCode = AuthorizationCode.createEmptyCode(); } } return state; } } /** * ?? ????? ??? * @return ????? ??? */ public SessionState getState() { synchronized (INSTANCE_LOCK) { return state; } } /** * ?? ????? ?? ??????? ??? ????. * @return ????? ?? ??????? true, ????? ?? false? ????. */ public final boolean isOpened() { final SessionState state = checkState(); return state == SessionState.OPENED; } private boolean isOpening() { final SessionState state = checkState(); return state == SessionState.OPENING; } /** * ?? ????? ?? ??????? ??? ????. * @return ????? ?? ??????? true, ????? ?? false? ????. */ public final boolean isClosed() { final SessionState state = checkState(); return state == SessionState.CLOSED; } /** * ?? ?? ???? ?? ?? * @return ?? ?? ???? ?? ?? */ public final RequestType getRequestType() { synchronized (INSTANCE_LOCK) { return requestType; } } /** * ?? ????? ??? ?? access token??? ????. * @return access token */ public final String getAccessToken() { synchronized (INSTANCE_LOCK) { return (accessToken == null) ? null : accessToken.getAccessTokenString(); } } /** * ? ???? ????. * @return ? ??? */ public static SharedPreferencesCache getAppCache() { final Session session = Session.getCurrentSession(); return session.appCache; } /** * authorization code ??? ?? ????. (authcode ??, state ??, accesstoken??) */ public void onAuthCodeCompleted(final AuthorizationResult result) { AuthorizationCode authCode = null; KakaoException exception = null; if(getRequestType() == null){ exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "session is closed during requesting authorization code. result will be ignored. state = " + getState()); } else if (result == null) { exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "the result of authorization code request is null."); } else { final String resultRedirectURL = result.getRedirectURL(); if(result.isSuccess()){ // ?? ???? redirect uri ???? if (resultRedirectURL != null && resultRedirectURL.startsWith(redirectUri)) { authCode = AuthorizationCode.createFromRedirectedUri(result.getRedirectUri()); // authorization code? ???????? ???? if (!authCode.hasAuthorizationCode()) { authCode = null; exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "the result of authorization code request does not have authorization code."); } // ?? ???? redirect uri ????? } else { exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "the result of authorization code request mismatched the registered redirect uri. msg = " + result.getResultMessage()); } } else if (result.isCanceled()) { exception = new KakaoException(ERROR_TYPE.CANCELED_OPERATiON, result.getResultMessage()); } else { exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, result.getResultMessage()); } } synchronized (INSTANCE_LOCK) { final SessionState previousState = state; if (authCode != null) { this.authorizationCode = authCode; state = SessionState.OPENING; // log? ??? callback??? ?????? ???. onStateChange(previousState, state, requestType, null, false); // request? ?????? ?????? request? reset requestType = null; } else { internalClose(exception, false); return; } } // request AccessToken open(null); } /** * access token ??? ?? ????. (access token ??, state ??) */ public void onAccessTokenCompleted(final AuthorizationResult result) { AccessToken resultAccessToken = null; KakaoException exception = null; if(getRequestType() == null){ exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "session is closed during requesting access token. result will be ignored. state = " + getState()); } else if (result == null) { exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "the result of access token request is null."); } else { if (result.isSuccess()) { resultAccessToken = result.getAccessToken(); if (!resultAccessToken.hasValidAccessToken()) { resultAccessToken = null; exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "the result of access token request is invalid access token."); } } else if (result.isCanceled()) { exception = new KakaoException(ERROR_TYPE.CANCELED_OPERATiON, result.getResultMessage()); } else { exception = new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, result.getResultMessage()); } } synchronized (INSTANCE_LOCK) { final SessionState previousState = state; if (resultAccessToken != null) { // refresh ????? refresh token??? ???? ???? ? ???? accessToken = resultAccessToken??? ?? ?????. accessToken.updateAccessToken(resultAccessToken); //authorization code? ?? ??? ???? ???. authorizationCode = AuthorizationCode.createEmptyCode(); saveTokenToCache(accessToken); state = SessionState.OPENED; onStateChange(previousState, state, requestType, null, false); requestType = null; } else { // refresh token?? ??????? ??? ? ??? ?????? ?? refresh token??? ??? ? ???? close??? ???. if(!(getRequestType().isRefreshingTokenRequest() && exception.isCancledOperation())){ internalClose(exception, false); } } } } private Session(final Context context){ if(context == null) throw new KakaoException(ERROR_TYPE.ILLEGAL_ARGUMENT, "cannot create Session without Context."); this.context = context; this.appKey = Utility.getMetadata(context, APP_KEY_PROPERTY); if(appKey == null) throw new KakaoException(ERROR_TYPE.MISS_CONFIGURATION, String.format("need to declare %s in your AndroidManifest.xml", APP_KEY_PROPERTY)); this.redirectUri = REDIRECT_URL_PREFIX + this.appKey + REDIRECT_URL_POSTFIX; this.appCache = new SharedPreferencesCache(context, appKey); this.sessionCallbacks = new ArrayList<SessionCallback>(); this.sessionCallbackHandler = new Handler(Looper.getMainLooper()); //?? callback??? main thread??? ?????????? ??. final Bundle loadedFromCache = appCache.load(); synchronized (INSTANCE_LOCK) { authorizationCode = AuthorizationCode.createEmptyCode(); accessToken = AccessToken.createFromCache(loadedFromCache); if (accessToken.hasValidAccessToken()) { this.state = SessionState.OPENED; } else if (accessToken.hasRefreshToken()) { this.state = SessionState.OPENING; } else { this.state = SessionState.CLOSED; internalClose(null, false); } } } /** * @param callback ??? ?? ?? */ private void addCallback(final SessionCallback callback) { synchronized (sessionCallbacks) { if (callback != null && !sessionCallbacks.contains(callback)) { sessionCallbacks.add(callback); } } } private void removeCallbacks(final List<SessionCallback> sessionCallbacksToBeRemoved) { synchronized (sessionCallbacks) { sessionCallbacks.removeAll(sessionCallbacksToBeRemoved); } } private void checkLoginActivity() throws KakaoException { Intent intent = new Intent(); intent.setClass(context, LoginActivity.class); if (!resolveIntent(intent)) { throw new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, String.format("need to declare %s as an activity in your AndroidManifest.xml", LoginActivity.class.getName())); } } private boolean resolveIntent(final Intent intent) { final ResolveInfo resolveInfo = Utility.resolveIntent(context, intent); return resolveInfo != null; } private Intent getLoginActivityIntent(final AuthorizationCodeRequest authCodeRequest) { final Intent intent = new Intent(context, LoginActivity.class); intent.putExtra(LoginActivity.CODE_REQUEST_KEY, authCodeRequest); return intent; } private Intent getLoginActivityIntent(final AccessTokenRequest accessTokenRequest) { final Intent intent = new Intent(context, LoginActivity.class); intent.putExtra(LoginActivity.TOKEN_REQUEST_KEY, accessTokenRequest); return intent; } private void requestLogin(final Intent intent) { boolean found = startLoginActivity(intent); if (!found) { internalClose(new KakaoException(ERROR_TYPE.AUTHORIZATION_FAILED, "failed to find LoginActivity"), false); } } private boolean startLoginActivity(final Intent intent) { try { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } catch (ActivityNotFoundException e) { return false; } return true; } /** * ????? close?? ?????? ??? ?? open??? ????. * @param kakaoException exception??? ????? close?? ?? ?? exception??? ???. * @param forced ?? close ??. ?? close???? ???? close? ???????? callback??? ????. */ private void internalClose(final KakaoException kakaoException, final boolean forced) { synchronized (INSTANCE_LOCK) { final SessionState previous = state; state = SessionState.CLOSED; requestType = null; authorizationCode = AuthorizationCode.createEmptyCode(); accessToken = AccessToken.createEmptyToken(); onStateChange(previous, state, requestType, kakaoException, forced); } if (this.appCache != null) { this.appCache.clearAll(); } // ?? ??????? cookie? ???? ??? CookieManager? ???? cookie? ? app?? ?? cookie?? ??? ????? ?????. // CookieManager? ??? CookieSyncManager? ???? ?? ??? ??. CookieSyncManager.createInstance(context.getApplicationContext()); CookieManager.getInstance().removeAllCookie(); } private void saveTokenToCache(final AccessToken newToken) { if (newToken != null && appCache != null) { newToken.saveAccessTokenToCache(appCache); } } private void onStateChange(final SessionState previousState, final SessionState newState, final RequestType requestType, final KakaoException exception, boolean forced) { if (!forced && (previousState == newState) && exception == null) { return; } Logger.getInstance().d(String.format("Session State changed : %s -> %s \n ex = %s, request_type = %s",previousState, newState ,(exception != null ? ", ex=" + exception.getMessage() : ""), requestType)); // ??????? opening??? state? ??? ??? ???. if( newState.isOpening()) return; final List<SessionCallback> dumpSessionCallbacks = new ArrayList<SessionCallback>(sessionCallbacks); Runnable runCallbacks = new Runnable() { public void run() { for(SessionCallback callback : dumpSessionCallbacks){ if (newState.isOpened()) callback.onSessionOpened(); else if (newState.isClosed()) callback.onSessionClosed(exception); } removeCallbacks(dumpSessionCallbacks); } }; //?? callback??? main thread??? ?????????? ??. sessionCallbackHandler.post(runCallbacks); } /** * @author MJ */ private static enum SessionState { /** * memory? cache?? session ??? ?? ?? ???. * ???? session?? ??? ? ??? session??? close(?? ?? ????, ??)? ???. * open({@link com.kakao.Session.RequestType#GETTING_AUTHORIZATION_CODE}) : ?? - {@link #OPENING}, ?? - ??? CLOSED * close(???? close) : ??? CLOSED */ CLOSED, /** * {@link #CLOSED}?????? token??? ?? ?? ?? authorization code? ?? ?? valid? authorization code? ??? ?? ???. * ??? ??????? ???????? refresh token??? ??? ?? ???. * open({@link com.kakao.Session.RequestType#GETTING_ACCESS_TOKEN} ??? {@link com.kakao.Session.RequestType#REFRESHING_ACCESS_TOKEN}) : ?? - {@link #OPENED}, ?? - {@link #CLOSED} * close(???? close) : {@link #CLOSED} */ OPENING, /** * access token??? ?????? ?? ?? valid access token??? ??? ?? ???. * ???? ?? : {@link #OPENING} * close(???? close) : {@link #CLOSED} */ OPENED; private boolean isClosed(){ return this == SessionState.CLOSED; } private boolean isOpening(){ return this == SessionState.OPENING; } private boolean isOpened(){ return this == SessionState.OPENED; } } private enum RequestType { GETTING_AUTHORIZATION_CODE, GETTING_ACCESS_TOKEN, REFRESHING_ACCESS_TOKEN; private boolean isAuthorizationCodeRequest() { return this == RequestType.GETTING_AUTHORIZATION_CODE; } private boolean isAccessTokenRequest() { return this == RequestType.GETTING_ACCESS_TOKEN; } private boolean isRefreshingTokenRequest() { return this == RequestType.REFRESHING_ACCESS_TOKEN; } } }