001package com.aispeech.dui.dds.auth; 002 003import android.content.Context; 004import android.os.Handler; 005import android.os.HandlerThread; 006import android.text.TextUtils; 007 008import com.aispeech.dui.BusClient; 009import com.aispeech.dui.dds.DDS; 010import com.aispeech.dui.manager.AIJavaException; 011import com.aispeech.dui.manager.AILog; 012import com.aispeech.dui.manager.BusLog; 013import com.aispeech.dui.manager.ThreadName; 014import com.aispeech.dui.oauth.AuthorizationManager; 015import com.aispeech.dui.oauth.CheckTokenListener; 016import com.aispeech.dui.oauth.OAuthSdk; 017import com.aispeech.dui.oauth.TokenListener; 018import com.aispeech.dui.oauth.TokenResult; 019import com.aispeech.libcomm.business.LocalKeys; 020import com.aispeech.libcomm.business.LocalKeysUtil; 021import com.aispeech.libcomm.business.call.AuthCallUtil; 022import com.aispeech.libcomm.business.topic.AuthTopicUtil; 023 024import org.json.JSONObject; 025 026import java.util.ArrayList; 027import java.util.Date; 028 029import okhttp3.Call; 030 031public class AccessTokenManager { 032 private static final String TAG = "oauth-TokenManager"; 033 private static final long CHECK_TIME = (long) (1000 * 60); //Timer心跳的时间间隔 60s 034 035 private static AccessTokenManager mInstance = new AccessTokenManager(); 036 private static final int MAX_REFRESH_RETRY_COUNT = 50; 037 private int mRefreshRetryCount = 0; 038 private long lastRefreshTime = -1L; 039 private static Context mContext; 040 private ArrayList<Call> requestTasks = new ArrayList<>(); 041 private int mRequestRetryCount = 0;// requestToken重复请求计数器 042 private static final int MAX_REQUEST_RETRY_COUNT = 2;// requestToken重复请求最大次数 043 private InnerThread mInnerThread = new InnerThread(); 044 private Runnable mCheckRefreshRunnable; 045 046 private AccessTokenManager() { 047 } 048 049 public static AccessTokenManager getInstance(Context context) { 050 mContext = context.getApplicationContext(); 051 return mInstance; 052 } 053 054 // 清除refreshToken、accessToken等信息 055 public void clearToken() { 056 AILog.d(TAG, "clearToken..."); 057 AuthorizationManager.getInstance().clearToken(); 058 } 059 060 public synchronized void start(String authCode, String codeVerifier, String redirectUri, TokenListener tokenListener) { 061 clearTasks(); 062 mRequestRetryCount = 0; 063 requestToken(authCode, codeVerifier, redirectUri, tokenListener); 064 } 065 066 public synchronized void release() { 067 mInnerThread.release(); 068 clearTasks(); 069 } 070 071 public void clearTasks() { 072 stopRefreshTokenTimer(); 073 resetRetryCount(); 074 for (Call call : requestTasks) { 075 call.cancel(); 076 } 077 requestTasks.clear(); 078 } 079 080 private void resetRetryCount() { 081 // 重置重试计数器 082 AILog.i(TAG, "reset retryCount"); 083 mRefreshRetryCount = 0; 084 } 085 086 private void requestToken(final String authCode, final String codeVerifier, final String redirectUri, final TokenListener tokenListener) { 087 // 检测入参是否正确 088 if (TextUtils.isEmpty(authCode) || TextUtils.isEmpty(codeVerifier) || TextUtils.isEmpty(redirectUri)) { 089 AILog.e(TAG, "requestToken params is null, return..."); 090 tokenListener.onError(404, "params is null"); 091 return; 092 } 093 Call call = AuthorizationManager.getInstance().requestToken(authCode, 094 codeVerifier, 095 redirectUri, 096 new TokenListener() { 097 @Override 098 public void onSuccess(TokenResult tokenResult) { 099 AILog.d(TAG, "requestToken :: expirein = " + tokenResult.getExpire() + " :: date = " + new Date(tokenResult.getExpire())); 100 updateAccessToken(tokenResult); 101 updateLastRefreshTime(tokenResult); 102 AILog.i(TAG, "request token success; lastRefreshTime = " + lastRefreshTime); 103 tokenListener.onSuccess(tokenResult); 104 startRefreshTokenTimer(); 105 resetRetryCount(); 106 AILog.i(TAG, "request token success end"); 107 } 108 109 @Override 110 public void onError(int errorId, String error) { 111 AILog.i(TAG, "requestToken error, errorId = " + errorId + ", error = " + error); 112 try { 113 JSONObject errorObj = new JSONObject(error); 114 String error_description = errorObj.optString("error_description"); 115 if (TextUtils.equals(error_description, "wrong_code")) { 116 // 这种情况下,说明code错误,不重试 117 clearTasks(); 118 tokenListener.onError(errorId, error); 119 AILog.i(TAG, "retry request onError by wrong_code, return..."); 120 return; 121 } 122 } catch (Exception e) { 123 e.printStackTrace(); 124 } 125 mRequestRetryCount++; 126 if (mRequestRetryCount > MAX_REQUEST_RETRY_COUNT) { 127 // 超过最大重试次数 128 clearTasks(); 129 tokenListener.onError(errorId, error); 130 AILog.i(TAG, "retry requestToken onError by max retry count, return..."); 131 return; 132 } 133 mInnerThread.post(new Runnable() { 134 @Override 135 public void run() { 136 AILog.d(TAG, "retry requestToken, count = " + mRequestRetryCount); 137 requestToken(authCode, codeVerifier, redirectUri, tokenListener); 138 } 139 }, 2000L * mRequestRetryCount); 140 } 141 }); 142 requestTasks.add(call); 143 } 144 145 public synchronized void refreshForInit() { 146 AILog.d(TAG, "refreshForInit"); 147 clearTasks(); 148 refreshToken(); 149 startRefreshTokenTimer(); 150 } 151 152 private void startRefreshTokenTimer() { 153 AILog.i(TAG, "startRefreshTokenTimer : "); 154 if (mCheckRefreshRunnable == null) { 155 mCheckRefreshRunnable = new Runnable() { 156 @Override 157 public void run() { 158 AILog.vfd(AILog.getLogId(), TAG, "checkRefreshToken..."); 159 checkRefreshToken(false); 160 mInnerThread.post(mCheckRefreshRunnable, CHECK_TIME); 161 } 162 }; 163 mInnerThread.post(mCheckRefreshRunnable, CHECK_TIME); 164 } 165 } 166 167 private void sendTokenRefreshFinishResult() { 168 AuthTopicUtil.publishTokenRefreshFinish(DDS.getInstance().getAgent().getBusClient()); 169 } 170 171 private void refreshToken() { 172 AILog.i(TAG, "refresh token"); 173 AuthorizationManager authorizationManager = AuthorizationManager.getInstance(); 174 final String refreshToken = authorizationManager.getRefreshToken(); 175 if (TextUtils.isEmpty(refreshToken)) { 176 AILog.w(TAG, "refresh token is null , please request token first!"); 177 sendTokenRefreshFinishResult(); 178 return; 179 } 180 Call call = authorizationManager.refreshToken(refreshToken, new TokenListener() { 181 @Override 182 public void onSuccess(TokenResult tokenResult) { 183 AILog.e(TAG, "refreshToken :: expirein = " + tokenResult.getExpire() + " :: date = " + new Date(tokenResult.getExpire())); 184 updateAccessToken(tokenResult); 185 updateLastRefreshTime(tokenResult); 186 sendTokenRefreshFinishResult(); 187 AILog.i(TAG, "refresh token success; lastRefreshTime = " + lastRefreshTime); 188 resetRetryCount(); 189 AILog.i(TAG, "refresh token success end"); 190 } 191 192 @Override 193 public void onError(final int errorId, final String error) { 194 AILog.i(TAG, "refreshToken error, errorId = " + errorId + ", error = " + error); 195 try { 196 JSONObject errorObj = new JSONObject(error); 197 String error_description = errorObj.optString("error_description"); 198 if (TextUtils.equals(error_description, "invalid_refresh_token")) { 199 // 这种情况下,说明refreshToken错误,不重试,超过30天没用 200 clearToken(); 201 resetRetryCount(); 202 AuthTopicUtil.publishTokenRefreshFail(DDS.getInstance().getAgent().getBusClient(), 70624, error); 203 AILog.i(TAG, "refreshToken onError by invalid_refresh_token, return..."); 204 return; 205 } 206 mRefreshRetryCount++; 207 if (mRefreshRetryCount > MAX_REFRESH_RETRY_COUNT) { 208 // 超过最大重试次数 209 resetRetryCount(); 210 AuthTopicUtil.publishTokenRefreshFail(DDS.getInstance().getAgent().getBusClient(), 70623, error); 211 AILog.i(TAG, "retry refreshToken onError by max retry count, return..."); 212 return; 213 } 214 mInnerThread.post(new Runnable() { 215 @Override 216 public void run() { 217 AILog.d(TAG, "retry refreshToken, count = " + mRefreshRetryCount); 218 refreshToken(); 219 } 220 }, 2000L * mRefreshRetryCount); 221 } catch (Exception e) { 222 e.printStackTrace(); 223 } 224 } 225 }); 226 requestTasks.add(call); 227 } 228 229 /** 230 * 检查accessToken是否可用 231 */ 232 public void checkAccessToken() { 233 try { 234 AILog.i(TAG, "checkAccessToken"); 235 if (OAuthSdk.getContext() == null) { 236 AILog.i(TAG, "OAuthSdk.getContext() is null, return..."); 237 return; 238 } 239 AuthorizationManager authorizationManager = AuthorizationManager.getInstance(); 240 String accessToken = authorizationManager.getAccessToken(); 241 if (TextUtils.isEmpty(accessToken)) { 242 AILog.w(TAG, "accessToken token is null , return..."); 243 return; 244 } 245 authorizationManager.checkAccessToken(accessToken, new CheckTokenListener() { 246 @Override 247 public void onSuccess(JSONObject resultObj) { 248 AILog.d(TAG, "checkAccessToken resultObj = " + resultObj.toString()); 249 String code = resultObj.optString("code"); 250 if (!TextUtils.equals("0", code)) { 251 // 失败的情况下重新刷新token 252 refreshForInit(); 253 } 254 } 255 256 @Override 257 public void onError(int errorId, String error) { 258 AILog.w(TAG, "checkAccessToken errorId = " + errorId + ", error = " + error); 259 } 260 }); 261 } catch (Exception e) { 262 AIJavaException.printException(e); 263 } 264 } 265 266 private synchronized void updateAccessToken(final TokenResult tokenResult) { 267 try { 268 if (tokenResult == null || TextUtils.isEmpty(tokenResult.getAccessToken())) { 269 AILog.e(TAG, "unknown error, will do refresh for reset"); 270 clearTasks(); 271 refreshForInit(); 272 return; 273 } 274 String accessToken = tokenResult.getAccessToken();//2个小时过期 275 BusClient bc = DDS.getInstance().getAgent().getBusClient(); 276 AuthCallUtil.callAuthUpdateAccessToken(bc, accessToken); 277 AILog.vfd(AILog.getLogId(), TAG, "accessToken is " + AuthorizationManager.getInstance().getAccessToken()); 278 AILog.vfd(AILog.getLogId(), TAG, "refreshToken is " + AuthorizationManager.getInstance().getRefreshToken()); 279 startRefreshTokenTimer(); 280 AuthTopicUtil.publishTokenRefreshFinish(bc); 281 AILog.i(TAG, "update AccessToken success end"); 282 } catch (Exception e) { 283 } 284 } 285 286 private void stopRefreshTokenTimer() { 287 // 停止刷新Toekn的timer 288 AILog.i(TAG, "stopRefreshTokenTimer : " + (mCheckRefreshRunnable == null)); 289 if (mCheckRefreshRunnable != null) { 290 if (mInnerThread.mHandler != null) { 291 mInnerThread.mHandler.removeCallbacksAndMessages(null); 292 } 293 mCheckRefreshRunnable = null; 294 } 295 } 296 297 private void updateLastRefreshTime(TokenResult tokenResult) { 298 lastRefreshTime = System.currentTimeMillis(); 299 try { 300 AILog.d(TAG, "updateLastRefreshTime => " + lastRefreshTime + ", K_EXPIRE_TIME => " + tokenResult.getExpire() * 1000); 301 LocalKeysUtil.getInstance().put(LocalKeys.OAuth.LOCAL_EXPIRE_TIME, tokenResult.getExpire() * 1000 + ""); 302 LocalKeysUtil.getInstance().put(LocalKeys.OAuth.LOCAL_REFRESH_TIME, lastRefreshTime + ""); 303 } catch (Exception e) { 304 com.aispeech.dui.manager.AIJavaException.printException(e); 305 } 306 } 307 308 public synchronized boolean checkRefreshToken(boolean isCheckByInit) { 309 try { 310 AILog.d(TAG, "checkRefreshToken isCheckByInit = " + isCheckByInit); 311 if (OAuthSdk.getContext() == null) { 312 String cliendId = LocalKeysUtil.getInstance().getString(LocalKeys.OAuth.LOCAL_AUTH_TYPE_CLIEND_ID, ""); 313 OAuthSdk.initialize(mContext, cliendId, "dds"); 314 } 315 lastRefreshTime = Long.valueOf(LocalKeysUtil.getInstance().getString(LocalKeys.OAuth.LOCAL_REFRESH_TIME, "-1")); 316 long expireTime = Long.valueOf(LocalKeysUtil.getInstance().getString(LocalKeys.OAuth.LOCAL_EXPIRE_TIME, "-1")); 317 if (isCheckByInit) { 318 AILog.d(TAG, "checkRefreshToken startRefreshTokenTimer for init"); 319 AILog.d(TAG, "do publish token_refresh.finish"); 320 refreshForInit(); 321 return true; 322 } 323 if (lastRefreshTime == -1 || expireTime == -1) { 324 AILog.e(TAG, "refresh anyway !!! checkTokenForRefresh lastRefreshTime = " + lastRefreshTime + " --- expireTime = " + expireTime); 325 refreshForInit(); 326 return true; 327 } 328 long t = System.currentTimeMillis() - lastRefreshTime; 329 AILog.d(TAG, "checkTokenForRefresh: " + 330 "System.currentTimeMillis() => " + System.currentTimeMillis() + 331 " lastRefreshTime =" + lastRefreshTime + 332 "; t = " + t + "; CHECK_TIME = " + CHECK_TIME + 333 "; expireTime = " + expireTime + 334 "; expireTime - PRE_REFRESH_TIME = " + (expireTime / 2)); 335 336 if (t <= 0 || t >= (expireTime / 2)) { 337 AILog.d(TAG, "checkTokenForRefresh call refreshForInit"); 338 refreshForInit(); 339 return true; 340 } 341 AILog.d(TAG, "checkRefreshToken end, drop..."); 342 } catch (Exception e) { 343 } 344 return false; 345 } 346 347 private class InnerThread { 348 // 内部服务线程 349 private HandlerThread mHandlerThread; 350 private Handler mHandler; 351 protected Object mLocakObj = new Object(); 352 353 // 初始化内部服务线程 354 private void initHandlerThread() { 355 AILog.i(BusLog.TAG, "init inner thread..."); 356 mHandlerThread = new HandlerThread(ThreadName.TOKEN_MANAGER); 357 mHandlerThread.start(); 358 mHandler = new Handler(mHandlerThread.getLooper()); 359 AILog.i(BusLog.TAG, "init inner thread success..."); 360 } 361 362 private void post(Runnable runnable, long delay) { 363 synchronized (mLocakObj) { 364 if (mHandlerThread == null) { 365 initHandlerThread(); 366 } 367 368 if (mHandler != null) { 369 mHandler.postDelayed(runnable, delay); 370 } 371 } 372 } 373 374 private void release() { 375 AILog.d(TAG, "release InnerThread"); 376 if (mHandlerThread != null) { 377 mHandlerThread.quit(); 378 } 379 mHandlerThread = null; 380 mHandler = null; 381 } 382 } 383 384}