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}