Java tutorial
/* * Copyright (C) 2011 mixi, Inc. All rights reserved. * * 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 jp.mixi.android.sdk; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog.Builder; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.Signature; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import android.webkit.WebView; import android.webkit.WebViewClient; import jp.mixi.android.IRemoteAuthenticator; import jp.mixi.android.sdk.util.UrlUtils; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import org.json.JSONException; import org.json.JSONObject; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.zip.GZIPInputStream; /** * MixiContainer? * */ @TargetApi(3) class MixiContainerImpl implements MixiContainer { /** ??? */ static final String ACCESS_TOKEN = "access_token"; /** ??? */ static final String REFRESH_TOKEN = "refresh_token"; /** ???? */ static final String EXPIRES = "expires_in"; /** ID??? */ private static final String CLIENT_ID = "client_id"; /** ?? */ private static final String MIXI_SESSION_KEY = "mixi_session"; /** access_token??? */ private static final String AUTH_HEADER_NAME = "Authorization"; private static final String STATE = "state"; private static final String CONTENT_TYPE = "Content-Type"; /** ????? */ private static final String RESPONSE = "response"; /** ?????????? */ private static final String OFFICIAL_APP_INTRODUCTION_PAGE = "http://mixi.jp/official_app_introduction.pl"; /** http.connection.timeout */ private static final String HTTP_PARAM_CONNECTION_TIMEOUT = "http.connection.timeout"; /** http.socket.timeout */ private static final String HTTP_PARAM_SOCKET_TIMEOUT = "http.socket.timeout"; private static final String ZIP_ENCODING = "gzip"; private static final String MIXI_APPS_SCOPE = "mixi_apps2"; private static final String DEPRECATED_SCOPE = "mixi_apps"; private static final String ERROR_STR = "error"; private static final String ERROR_CODE_STR = "errorCode"; private static final String ERROR_MESSAGE_STR = "errorMessage"; private static final String ACCOUNT_EXCEPTION = "mixiAccountException"; /** ??? */ private static final int SUPPORTED_VERSION = 19; /** ??? */ private static final int PAYMENT_SUPPORTED_VERSION = 19; /** */ private static final String TAG = "MixiContainerImpl"; /** ???? */ private static final String OFFICIAL_PACKAGE = "jp.mixi"; private static final int VALIDATE_OFFICIAL_FOR_ACTIVITY = 1; private static final int VALIDATE_OFFICIAL_FOR_SERVICE = 2; /** ???Activity */ private static final String AUTH_ACTIVITY = "jp.mixi.android.clientauthenticator.ClientAuthenticatorActivity"; /** Activity */ private static final String PAYMENT_ACTIVITY = "jp.mixi.android.sdk.payment.PaymentActivity"; /** ???? */ private static final String MIXI_OFFICIAL_SIGNATURE = "30820265308201cea00302010202044d0b3f7b300d06092a864886f70d01010505003077310b3009" + "060355040613024a50310e300c06035504081305546f6b796f3110300e0603550407130753686962" + "75796131123010060355040a13096d69786920496e632e311a3018060355040b131153797374656d" + "204465706172746d656e74311630140603550403130d59756b692046756a6973616b69301e170d31" + "30313231373130343631395a170d3430313230393130343631395a3077310b300906035504061302" + "4a50310e300c06035504081305546f6b796f3110300e060355040713075368696275796131123010" + "060355040a13096d69786920496e632e311a3018060355040b131153797374656d20446570617274" + "6d656e74311630140603550403130d59756b692046756a6973616b6930819f300d06092a864886f7" + "0d010101050003818d0030818902818100bbdfdb3dbb8aaaee39a05cf6543359fe5bab780ae5362f" + "34e3777d0a1a8e8dc1b4fdf0c9e1046c54bef4f367ce59ab87ea04e7d81a3fc10a173c20f2250cf2" + "77b844447eef14c893d581f189db8b43ce78798665edde516bb483c45d9bafead9530a89257d3b3d" + "ca42d56f40d468ed1f6bb95ceb605eb215d328727521bbdd5b0203010001300d06092a864886f70d" + "0101050500038181000e0180edcf89f27790b87f34f890ec71f15c8c7340836b5079b7319062a5e3" + "1c1a77fe9e75f190732094e5466ad10cf4df06a9f8d5917c27bb2b9502885e877c8e239100c50bf0" + "5b5db268f9901090d5cf294d5e887853b1271a86590e831b85ccd858321bbbbd70601ad8656aaad2" + "3ac1587afd318b5dd4cc052b43809a51ca"; // field /** mixi??HttpClient */ private HttpClient mHttpClient; /** http.connection.timeout? */ private int mConnectionTimeout; /** http.socket.timeout? */ private int mSocketTimeout; private int mSelector; /** ????CallbackListener */ private CallbackListener mAuthCallbackListener; /** (revoke)?CallbackListener */ private CallbackListener mRevokeCallbackListener; /** API???CallbackListener */ private CallbackListener mPaymentCallbackListener; /** ID */ private final String mClientId; /** */ private ContextWrapper mContextWrapper; /** authorize?intent? */ private int mActivityCode; /** logout?intent? */ private int mRevokeCode; /** requestPayment?intent? */ private int mPaymentRequestCode; /** access token */ private String mAccessToken; /** refresh token */ private String mRefreshToken; /** ?? */ private long mExpiresIn = 0; /** */ private AppsCounter mAppsCounter; private AppsCounter mAppsStartCounter; /** ???authorize?? */ private String[] mDefaultScope; /** ? */ private IRemoteAuthenticator mRemoteAuth; /** ?? */ private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { mRemoteAuth = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mRemoteAuth = IRemoteAuthenticator.Stub.asInterface(service); } }; /** * Mixi Graph API??????ID???. * * @param config ??ID */ private MixiContainerImpl(Config config) { if (config == null || config.clientId == null) { throw new IllegalArgumentException("You must specify your client ID."); } switch (config.selector) { case Config.APPLICATION: mDefaultScope = new String[] { MIXI_APPS_SCOPE }; break; case Config.GRAPH_API: break; default: throw new IllegalArgumentException("Config.selector is unknown value:" + config.selector); } mClientId = config.clientId; mConnectionTimeout = config.connectionTimeout; mSocketTimeout = config.socketTimeout; mSelector = config.selector; } /** * ? * * @param config ?? * @return MixiContainer? */ public static MixiContainerImpl getInstance(Config config) { Log.v(TAG, "get new instance"); return new MixiContainerImpl(config); } @Override public void authorize(final Activity activity, String[] permissions, int activityCode, final CallbackListener listener) { mAuthCallbackListener = new CallbackListener() { @Override public void onFatal(ErrorInfo e) { listener.onFatal(e); } @Override public void onError(ErrorInfo e) { listener.onError(e); } @Override public void onComplete(Bundle bundle) { // token?? setAccessToken(bundle.getString(ACCESS_TOKEN)); setRefreshToken(bundle.getString(REFRESH_TOKEN)); setAccessExpiresIn(bundle.getString(EXPIRES)); saveSession(activity); listener.onComplete(new Bundle()); } @Override public void onCancel() { listener.onCancel(); } }; for (String permission : permissions) { if (DEPRECATED_SCOPE.equals(permission)) { throw new IllegalArgumentException("use permission mixi_apps2"); } } mActivityCode = activityCode; // ???? if (startSingleSignOn(activity, activityCode, permissions)) { saveSession(activity); Log.d(TAG, "SingleSignOn done"); return; } Log.e(TAG, "official application not found"); listener.onFatal(new ErrorInfo("official application not found")); } public void authorize(Activity activity, int activityCode, final CallbackListener listener) { authorize(activity, mDefaultScope, activityCode, listener); } @Override public boolean isAuthorized() { return (getAccessToken() != null); } @Override public boolean init(ContextWrapper context) { this.mContextWrapper = context; if (!validatePermission(context)) { return false; } Intent intent = new Intent(IRemoteAuthenticator.class.getName()); if (!validateOfficialAppsForIntent(mContextWrapper, intent, VALIDATE_OFFICIAL_FOR_SERVICE, SUPPORTED_VERSION)) { // ?????????????? new MixiDialog(context, OFFICIAL_APP_INTRODUCTION_PAGE, new HashMap<String, String>(), new CallbackListener() { @Override public void onFatal(ErrorInfo e) { Log.v(TAG, "OFFICIAL_APP_INTRODUCTION_PAGE onFatal"); } @Override public void onError(ErrorInfo e) { Log.v(TAG, "OFFICIAL_APP_INTRODUCTION_PAGE onError"); } @Override public void onComplete(Bundle values) { Log.v(TAG, "OFFICIAL_APP_INTRODUCTION_PAGE onComplete"); } @Override public void onCancel() { Log.v(TAG, "OFFICIAL_APP_INTRODUCTION_PAGE onCancel"); } }, true).show(); return false; } setUpCounter(); if (mRemoteAuth == null) { // ?? restoreSession(mContextWrapper); return bindRemoteService(mContextWrapper, new Intent(IRemoteAuthenticator.class.getName()), mServiceConnection); } return true; } private boolean validatePermission(Context context) { if (context .checkCallingOrSelfPermission(Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "No permission Manifest.permission.INTERNET"); Builder alertBuilder = new Builder(context); alertBuilder.setMessage(context.getString(R.string.no_permission_error)); alertBuilder.create().show(); return false; } return true; } private boolean bindRemoteService(ContextWrapper context, Intent intent, ServiceConnection connection) { return context.bindService(intent, connection, Context.BIND_AUTO_CREATE); } @Override public void close(ContextWrapper context) { if (mRemoteAuth != null) { context.unbindService(mServiceConnection); mRemoteAuth = null; } } @Override public void showDialog(Context context, String action, Map<String, String> parameters, final CallbackListener listener) { showDialog(context, action, parameters, listener, true); } @Override public void showDialog(final Context context, String action, Map<String, String> parameters, final CallbackListener listener, final boolean isCancelable) { Uri uri = Uri.parse(Constants.GRAPH_BASE_URL); android.net.Uri.Builder builder = uri.buildUpon(); builder.appendEncodedPath("dialog" + action); for (Entry<String, String> param : parameters.entrySet()) { builder.appendQueryParameter(param.getKey(), param.getValue()); } final String url = builder.build().toString(); AsyncTask<String, Void, Void> tasc = new AsyncTask<String, Void, Void>() { private Exception e; @Override protected Void doInBackground(String... params) { try { refreshToken(); } catch (RemoteException e) { this.e = e; } catch (ApiException e) { this.e = e; } return null; } @Override protected void onPostExecute(Void res) { if (e == null) { HashMap<String, String> map = new HashMap<String, String>(); map.put("oauth_token", getAccessToken()); new MixiDialog(context, url, map, listener, isCancelable).show(); } else { Log.v(TAG, "refresh token error"); listener.onFatal(new ErrorInfo(e)); } } }; tasc.execute(url); } @Override public void send(String endpointPath, CallbackListener listener) { send(endpointPath, new HashMap<String, String>(), listener); } @Override public void send(String endpointPath, Map<String, String> parameters, CallbackListener listener) { send(endpointPath, HttpMethod.GET, parameters, listener); } @Override public void send(final String endpointPath, final JSONObject json, final CallbackListener listener) { send(endpointPath, HttpMethod.POST, json, listener); } @Override public void send(final String endpointPath, HttpMethod method, final JSONObject json, final CallbackListener listener) { try { HttpUriRequest httpMethod = getHttpMethod(endpointPath, json, method); requestAsync(httpMethod, listener, true); } catch (IOException e) { listener.onFatal(new ErrorInfo(e)); } } @Override public void send(String endpointPath, HttpMethod method, CallbackListener listener) { send(endpointPath, method, new HashMap<String, String>(), listener); } @Override public void send(String endpointPath, HttpMethod method, Map<String, String> parameters, CallbackListener listener) { try { HttpUriRequest httpMethod = getHttpMethod(endpointPath, parameters, method); requestAsync(httpMethod, listener, true); } catch (IOException e) { listener.onFatal(new ErrorInfo(e)); } } @Override public void send(final String endpointPath, final String contentType, final InputStream stream, final long length, final CallbackListener listener) { AsyncTask<Void, Void, Void> tasc = new AsyncTask<Void, Void, Void>() { private Exception e; @Override protected Void doInBackground(Void... params) { try { refreshToken(); } catch (RemoteException e) { this.e = e; } catch (ApiException e) { this.e = e; } return null; } @Override protected void onPostExecute(Void res) { if (e == null) { send(endpointPath, contentType, HttpMethod.POST, stream, length, listener); } else { Log.v(TAG, "refresh token error"); listener.onFatal(new ErrorInfo(e)); } } }; tasc.execute(null); } @Override public void send(String endpointPath, String contentType, HttpMethod method, InputStream stream, long length, CallbackListener listener) { try { HttpUriRequest httpMethod = getHttpMethod(endpointPath, contentType, method, stream, length); requestAsync(httpMethod, listener, true); } catch (IOException e) { listener.onFatal(new ErrorInfo(e)); } } @Override public void authorizeCallback(int requestCode, int resultCode, Intent data) { // ??????? if (requestCode == mActivityCode) { callbackExecute(resultCode, data, mAuthCallbackListener); return; } else if (requestCode == mRevokeCode) { callbackExecute(resultCode, data, mRevokeCallbackListener); return; } } private void callbackExecute(int resultCode, Intent data, CallbackListener listener) { if (resultCode == Activity.RESULT_OK) { listener.onComplete(data.getExtras()); return; } else { if (data != null && data.hasExtra(ERROR_STR)) { Log.d(TAG, data.getStringExtra(ERROR_STR)); int code = data.getIntExtra(ERROR_CODE_STR, 0); if (code == ErrorInfo.SERVER_ERROR) { listener.onError(new ErrorInfo(data.getStringExtra(ERROR_MESSAGE_STR), code)); return; } else if (code == ErrorInfo.OTHER_ERROR && ACCOUNT_EXCEPTION.equals(data.getStringExtra(ERROR_STR))) { listener.onError(new ErrorInfo(data.getStringExtra(ERROR_MESSAGE_STR), ErrorInfo.OFFICIAL_APP_ACCOUNT_ERROR)); return; } listener.onFatal(new ErrorInfo(data.getStringExtra(ERROR_MESSAGE_STR), code)); return; } Log.d(TAG, "Login canceled by user."); listener.onCancel(); return; } } @Override public void logout(final Activity activity, int activityCode, final CallbackListener listener) { mRevokeCode = activityCode; // ?????????????????? mRevokeCallbackListener = new CallbackListener() { @Override public void onFatal(ErrorInfo e) { listener.onFatal(e); } @Override public void onError(ErrorInfo e) { listener.onError(e); } @Override public void onComplete(Bundle values) { deleteSession(activity); listener.onComplete(values); } @Override public void onCancel() { listener.onCancel(); } }; Map<String, String> param = new HashMap<String, String>(); param.put(CLIENT_ID, mClientId); String refreshToken = getRefreshToken(); if (refreshToken == null) { mRevokeCallbackListener.onFatal(new ErrorInfo("not authorized", ErrorInfo.REVOKE_AUTHORIZE)); return; } param.put("token", refreshToken); Intent intent = new Intent(); intent.putExtra("mode", "revoke"); intent.setClassName(OFFICIAL_PACKAGE, AUTH_ACTIVITY); intent.putExtra(CLIENT_ID, mClientId); intent.putExtra(REFRESH_TOKEN, refreshToken); if (!validateOfficialAppsForIntent(activity, intent, VALIDATE_OFFICIAL_FOR_ACTIVITY, SUPPORTED_VERSION)) { mRevokeCallbackListener .onFatal(new ErrorInfo("official application not found", ErrorInfo.OFFICIAL_APP_NOT_FOUND)); return; } try { activity.startActivityForResult(intent, activityCode); } catch (ActivityNotFoundException e) { Log.e(TAG, e.getMessage(), e); mRevokeCallbackListener.onFatal(new ErrorInfo(e)); } } /** * * * @param context */ private void deleteSession(final Context context) { new Thread(new Runnable() { @Override public void run() { SharedPreferences sp = context.getSharedPreferences(MIXI_SESSION_KEY, Context.MODE_PRIVATE); if (sp != null) { sp.edit().clear().commit(); } // ? setAccessToken(null); setRefreshToken(null); setAccessExpiresIn(0L); } }).start(); } @Override public void requestPayment(final Activity activity, final PaymentParameter param, final int activityCode, final CallbackListener listener) { mPaymentCallbackListener = listener; mPaymentRequestCode = activityCode; if (!validatePaymentParam(param)) { listener.onError(new ErrorInfo("parameter invalid", ErrorInfo.OTHER_ERROR)); return; } Intent intent = new Intent(); intent.setClassName(OFFICIAL_PACKAGE, PAYMENT_ACTIVITY); intent.putExtras(convertPaymentBundle(param)); if (!validateOfficialAppsForIntent(activity, intent, VALIDATE_OFFICIAL_FOR_ACTIVITY, PAYMENT_SUPPORTED_VERSION)) { listener.onFatal(new ErrorInfo(activity.getString(R.string.err_unsupported_versions), ErrorInfo.OFFICIAL_APP_NOT_FOUND)); return; } try { activity.startActivityForResult(intent, activityCode); } catch (ActivityNotFoundException e) { Log.v(TAG, e.getMessage()); listener.onFatal(new ErrorInfo(e)); } } private boolean validatePaymentParam(PaymentParameter param) { boolean flg = true; if (param.callbackUrl == null) { flg = false; } if (param.inventoryCode == null) { flg = false; } if (param.itemId == null) { flg = false; } if (param.itemName == null) { flg = false; } if (param.itemPrice <= 0) { flg = false; } if (param.signature == null) { flg = false; } return flg; } /** * PaymentParameterintent?bundle?? * * @param param * @return */ private Bundle convertPaymentBundle(PaymentParameter param) { Bundle bundle = new Bundle(); bundle.putString(PaymentParameter.CLIENT_ID_NAME, mClientId); bundle.putString(PaymentParameter.CALLBACK_URL_NAME, param.callbackUrl); bundle.putString(PaymentParameter.INVENTORY_CODE_NAME, param.inventoryCode); bundle.putBoolean(PaymentParameter.IS_TEST_NAME, param.isTest); bundle.putString(PaymentParameter.ITEM_ID_NAME, param.itemId); bundle.putString(PaymentParameter.ITEM_NAME_NAME, param.itemName); bundle.putInt(PaymentParameter.ITEM_PRICE_NAME, param.itemPrice); bundle.putString(PaymentParameter.SIGNATURE_NAME, param.signature); bundle.putInt(PaymentParameter.VERSION_NAME, param.getVertion()); return bundle; } @Override public void paymentCallback(int requestCode, int resultCode, Intent data) { if (requestCode == mPaymentRequestCode) { callbackExecute(resultCode, data, mPaymentCallbackListener); } } /** * ??. * * @param context * @return */ private void saveSession(final Context context) { new Thread(new Runnable() { @Override public void run() { Editor editor = context.getSharedPreferences(MIXI_SESSION_KEY, Context.MODE_PRIVATE).edit(); editor.putString(ACCESS_TOKEN, getAccessToken()); editor.putLong(EXPIRES, getAccessExpiresIn()); editor.putString(REFRESH_TOKEN, getRefreshToken()); editor.putString(CLIENT_ID, mClientId); editor.commit(); } }).start(); } /** * ? * * @param contextWrapper ?activity * @param listener ???????????????(?????????) * @return ???????true * @throws RemoteException */ boolean restoreSession(final ContextWrapper contextWrapper) { new Thread(new Runnable() { @Override public void run() { SharedPreferences prefs = contextWrapper.getSharedPreferences(MIXI_SESSION_KEY, Context.MODE_PRIVATE); String appId = prefs.getString(CLIENT_ID, null); if (appId == null) { return; } setAccessToken(prefs.getString(ACCESS_TOKEN, null)); setRefreshToken(prefs.getString(REFRESH_TOKEN, null)); setAccessExpiresIn(prefs.getLong(EXPIRES, 0)); if (isAuthorized()) { runAppCounter(); } } }).start(); if (getAccessToken() == null) { return false; } return true; } /** * ? * * @param activity ?Activity * @param intent Intent(?) * @return */ private boolean validateOfficialAppsForIntent(Context activity, Intent intent, int target, int supportedVersion) { String packageName = null; if (target == VALIDATE_OFFICIAL_FOR_ACTIVITY) { ResolveInfo resolveInfo = activity.getPackageManager().resolveActivity(intent, 0); if (resolveInfo == null) { Log.d(TAG, "official application not found"); return false; } packageName = resolveInfo.activityInfo.packageName; } else if (target == VALIDATE_OFFICIAL_FOR_SERVICE) { ResolveInfo resolveInfo = activity.getPackageManager().resolveService(intent, 0); if (resolveInfo == null) { Log.d(TAG, "official application not found"); return false; } packageName = resolveInfo.serviceInfo.packageName; } else { Log.d(TAG, "do not support option"); return false; } try { PackageInfo packageInfo = activity.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); if (packageInfo.versionCode < supportedVersion) { Log.d(TAG, "Unsupported version" + packageInfo.versionCode); return false; } // ????? return validateSignature(packageInfo.signatures); } catch (NameNotFoundException e) { Log.d(TAG, "NameNotFoundException"); return false; } } /** * ?? * * @param signatures ???? * @return ??????true */ private boolean validateSignature(Signature[] signatures) { if (signatures != null && signatures.length == 1) { if (MIXI_OFFICIAL_SIGNATURE.equals(signatures[0].toCharsString())) { Log.v(TAG, "signature check OK"); return true; } } Log.d(TAG, "signature check NG"); return false; } /** * HTTP?? * * @param endpointPath ? * @param json ?json * @param httpMethod http * @return HttpUriRequest * @throws IOException json????? */ private HttpUriRequest getHttpMethod(String endpointPath, JSONObject json, HttpMethod httpMethod) throws IOException { HttpEntity entity = createHttpEntity(json); switch (httpMethod) { case POST: HttpPost postMethod = new HttpPost(Constants.GRAPH_BASE_URL + endpointPath); postMethod.setEntity(entity); return postMethod; case PUT: HttpPut putMethod = new HttpPut(Constants.GRAPH_BASE_URL + endpointPath); putMethod.setEntity(entity); return putMethod; default: Log.e(TAG, "Unsupported http method"); throw new IllegalArgumentException("Unsupported HttpMethod parameter:" + httpMethod); } } private HttpEntity createHttpEntity(JSONObject param) throws UnsupportedEncodingException { StringEntity entity = new StringEntity(param.toString(), HTTP.UTF_8); entity.setContentType("application/json"); return entity; } /** * HTTP?? * * @param endpointPath ? * @param parameters * @param httpMethod http * @return HttpUriRequest * @throws IOException parameters????? */ private HttpUriRequest getHttpMethod(String endpointPath, Map<String, String> parameters, HttpMethod httpMethod) throws IOException { HttpUriRequest method = null; String url = null; android.net.Uri.Builder builder = null; switch (httpMethod) { case GET: builder = Uri.parse(Constants.GRAPH_BASE_URL + endpointPath).buildUpon(); builder.encodedQuery(UrlUtils.encodeUrl(parameters)); url = builder.build().toString(); method = new HttpGet(url); break; case POST: url = Constants.GRAPH_BASE_URL + endpointPath; HttpPost post = new HttpPost(url); ArrayList<NameValuePair> getParams = new ArrayList<NameValuePair>(); for (String key : parameters.keySet()) { if (!key.equals(CONTENT_TYPE)) { getParams.add(new BasicNameValuePair(key, parameters.get(key))); } } post.setEntity(new UrlEncodedFormEntity(getParams, HTTP.UTF_8)); method = post; break; case PUT: ArrayList<NameValuePair> putParams = new ArrayList<NameValuePair>(); HttpPut put = new HttpPut(Constants.GRAPH_BASE_URL + endpointPath); for (String key : parameters.keySet()) { if (!key.equals(CONTENT_TYPE)) { putParams.add(new BasicNameValuePair(key, parameters.get(key))); } } put.setEntity(new UrlEncodedFormEntity(putParams, HTTP.UTF_8)); method = put; break; case DELETE: builder = Uri.parse(Constants.GRAPH_BASE_URL + endpointPath).buildUpon(); builder.encodedQuery(UrlUtils.encodeUrl(parameters)); url = builder.build().toString(); method = new HttpDelete(url); break; default: Log.e(TAG, "Unsupported http method"); throw new UnsupportedOperationException("Unsupported http method:" + method); } return method; } /** * HTTP?? * * @param endpointPath ? * @param stream ?InputStream * @param length InputStream?? * @return HttpUriRequest */ private HttpUriRequest getHttpMethod(String endpointPath, String contentType, HttpMethod method, InputStream stream, long length) throws IOException { InputStreamEntity entity = new InputStreamEntity(stream, length); entity.setContentType(contentType); switch (method) { case POST: HttpPost postMethod = new HttpPost(Constants.GRAPH_BASE_URL + endpointPath); postMethod.setEntity(entity); return postMethod; case PUT: HttpPut putMethod = new HttpPut(Constants.GRAPH_BASE_URL + endpointPath); putMethod.setEntity(entity); return putMethod; default: Log.e(TAG, "Unsupported http method"); throw new IllegalArgumentException("Unsupported HttpMethod parameter:" + method); } } /** * ?? * * @param method */ private Header[] getCommonHeaders() { Header[] headers = new Header[3]; headers[0] = new BasicHeader(AUTH_HEADER_NAME, "OAuth " + mAccessToken); headers[1] = new BasicHeader("User-Agent", Constants.USER_AGENT); headers[2] = new BasicHeader("Accept-Encoding", ZIP_ENCODING); return headers; } /** * ???? * * @param method ?http * @param listener ??? * @param doRetry ???? * @return ???? * @throws FileNotFoundException * @throws MalformedURLException * @throws IOException * @throws JSONException * @throws RemoteException * @throws ApiException */ private String request(HttpUriRequest method, final CallbackListener listener, boolean doRetry) throws FileNotFoundException, MalformedURLException, IOException, JSONException, RemoteException, ApiException { HttpClient client = getHttpClient(); HttpEntity entity = null; try { if (getAccessExpiresIn() < System.currentTimeMillis() + mSocketTimeout + mConnectionTimeout) { refreshToken(); } method.setHeaders(getCommonHeaders()); HttpResponse res = client.execute(method); StatusLine status = res.getStatusLine(); entity = res.getEntity(); // gzip?????? if (isGZipEntity(entity)) { entity = decompressesGZipEntity(entity); } String responseBody = null; switch (status.getStatusCode()) { case HttpStatus.SC_OK: case HttpStatus.SC_CREATED: // OK?string????? Log.v(TAG, "HTTP OK"); return EntityUtils.toString(entity, HTTP.UTF_8); case HttpStatus.SC_UNAUTHORIZED: if (isExpiredToken(res, status) && doRetry) { refreshToken(); entity.consumeContent(); entity = null; return request(method, listener, false); } responseBody = EntityUtils.toString(entity, HTTP.UTF_8); if (responseBody != null && responseBody.length() > 0) { throw new ApiException(status, responseBody); } else { throw new ApiException(status, status.getReasonPhrase()); } default: responseBody = EntityUtils.toString(entity, HTTP.UTF_8); if (responseBody != null && responseBody.length() > 0) { throw new ApiException(status, responseBody); } else { throw new ApiException(status, status.getReasonPhrase()); } } } finally { // if (entity != null) { entity.consumeContent(); } if (method != null) { method.abort(); } } } /** * ??gzip * * @param entity ??HttpEntity * @return gzip???? */ private boolean isGZipEntity(HttpEntity entity) { Header header = entity.getContentEncoding(); if (header == null) { return false; } String value = header.getValue(); return (value != null && value.contains(ZIP_ENCODING)); } /** * gzip??HttpEntity? * * @param entity ??HttpEntity * @return ?HttpEntity * @throws IllegalStateException * @throws IOException */ private HttpEntity decompressesGZipEntity(HttpEntity entity) throws IllegalStateException, IOException { return new InputStreamEntity(new GZIPInputStream(entity.getContent()), 0); } private synchronized HttpClient getHttpClient() { if (mHttpClient != null) { return mHttpClient; } DefaultHttpClient defaultHttpClient = new DefaultHttpClient(); ClientConnectionManager mgr = defaultHttpClient.getConnectionManager(); HttpParams params = defaultHttpClient.getParams(); mHttpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(params, mgr.getSchemeRegistry()), params); mHttpClient.getParams().setParameter(HTTP_PARAM_CONNECTION_TIMEOUT, mConnectionTimeout); mHttpClient.getParams().setParameter(HTTP_PARAM_SOCKET_TIMEOUT, mSocketTimeout); return mHttpClient; } /** * ????? * * @param res * @param status * @return */ private boolean isExpiredToken(HttpResponse res, StatusLine status) { // check WWW-Authenticate header and obtain error-reason if it exists Header[] headers = res.getHeaders("WWW-Authenticate"); if (headers == null || headers.length == 0) { return false; } Header authHeader = headers[0]; Log.v(TAG, "AUTH HEADER : " + authHeader.getName()); for (HeaderElement elem : authHeader.getElements()) { Log.v(TAG, "AUTH HEADER ELEMENT : " + elem.getName()); if ("OAuth error".equals(elem.getName())) { String reason = elem.getValue(); Log.v(TAG, "reason: " + reason); if (reason.startsWith("'expired")) { return true; } } } return false; } /** * Handler?post? * * @param method ?http * @param listener ??? * @param doRetry ???? */ private void requestAsync(final HttpUriRequest method, final CallbackListener listener, final boolean doRetry) { new AsyncRequester(method, listener, doRetry).execute(); } private boolean startSingleSignOn(Activity activity, int activityCode, String[] permissions) { // ?????intent???? Intent intent = new Intent(); intent.putExtra("mode", "authorize"); intent.setClassName(OFFICIAL_PACKAGE, AUTH_ACTIVITY); intent.putExtra(CLIENT_ID, mClientId); // ?? if (permissions != null && permissions.length > 0) { intent.putExtra("scope", TextUtils.join(" ", permissions)); } // state? intent.putExtra(STATE, String.valueOf(new Random().nextLong() >>> 1)); if (!validateOfficialAppsForIntent(activity, intent, VALIDATE_OFFICIAL_FOR_ACTIVITY, SUPPORTED_VERSION)) { return false; } try { activity.startActivityForResult(intent, activityCode); } catch (ActivityNotFoundException e) { Log.v(TAG, e.getMessage()); return false; } return true; } private void setAccessToken(String token) { this.mAccessToken = token; } private String getAccessToken() { return this.mAccessToken; } private void setRefreshToken(String token) { this.mRefreshToken = token; } private String getRefreshToken() { return this.mRefreshToken; } /** * ??? * * @param expiresIn mixi?????? */ private void setAccessExpiresIn(String expiresIn) { if (expiresIn != null && !expiresIn.equals("0")) { setAccessExpiresIn(System.currentTimeMillis() + (Long.parseLong(expiresIn) * 1000)); } else { setAccessExpiresIn(0L); } } /** * ??? * * @param expiresIn ???long */ private void setAccessExpiresIn(Long expiresIn) { this.mExpiresIn = expiresIn; } /** * ??? * * @return ??ms?long */ private long getAccessExpiresIn() { return this.mExpiresIn; } /** * ? * * @throws RemoteException * @throws ApiException */ private synchronized void refreshToken() throws RemoteException, ApiException { Log.v(TAG, "refreshToken start"); Bundle bundle = new Bundle(); bundle.putString(REFRESH_TOKEN, getRefreshToken()); bundle.putString(CLIENT_ID, mClientId); // ????? if (mRemoteAuth == null) { Log.v(TAG, "RemoteAuthenticator is not bind"); throw new ApiException(ErrorInfo.OFFICIAL_APP_NOT_FOUND, "RemoteAuthenticator is not bind"); } Bundle response = mRemoteAuth.tokenRefresh(bundle); if (response.containsKey(ERROR_STR)) { Log.w(TAG, response.getString(ERROR_STR)); throw new ApiException(response.getInt(ERROR_CODE_STR), response.getString(ERROR_MESSAGE_STR)); } String accessToken = response.getString(ACCESS_TOKEN); String refreshToken = response.getString(REFRESH_TOKEN); String expire = response.getString(EXPIRES); setAccessToken(accessToken); setRefreshToken(refreshToken); setAccessExpiresIn(expire); if (mContextWrapper != null) { saveSession(mContextWrapper); } } private synchronized void setupAppCounter() { if (mAppsCounter == null) { mAppsCounter = new AppsCounter(new HttpPost(Constants.COUNTER_URL)); } if (mRemoteAuth != null) { runAppCounter(); } } private synchronized void setUpCounter() { if (mSelector == Config.APPLICATION) { if (mAppsStartCounter == null) { mAppsStartCounter = new AppsCounter(new HttpPost(Constants.APP_COUNTER_URL), new CallbackListener() { @Override public void onFatal(ErrorInfo e) { } @Override public void onError(ErrorInfo e) { } @Override public void onComplete(Bundle values) { mAppsStartCounter = null; } @Override public void onCancel() { } }); } if (mRemoteAuth != null) { runAppCounter(); } } } private synchronized void runAppCounter() { if (mAppsCounter != null) { mAppsCounter.execute(); } if (mAppsStartCounter != null) { mAppsStartCounter.execute(); } } /** * ???http???? */ private class AsyncRequester extends AsyncTask<Void, Void, Bundle> { private HttpUriRequest mMethod; private CallbackListener mListener; private boolean mDoRetry; private boolean mIsFatal; private ErrorInfo mException; public AsyncRequester(final HttpUriRequest method, final CallbackListener listener, final boolean doRetry) { mMethod = method; mListener = listener; mDoRetry = doRetry; } @Override protected Bundle doInBackground(Void... params) { try { Log.v(TAG, mMethod.getURI().toString()); String response = request(mMethod, mListener, mDoRetry); Bundle bundle = new Bundle(); bundle.putString(RESPONSE, response); return bundle; } catch (IOException e) { mIsFatal = true; mException = new ErrorInfo(e); } catch (JSONException e) { mIsFatal = true; mException = new ErrorInfo(e); } catch (RemoteException e) { mIsFatal = true; mException = new ErrorInfo(e); } catch (ApiException e) { mException = new ErrorInfo(e.getMessage(), e.getCode()); } return null; } @Override protected void onPostExecute(Bundle result) { if (result != null) { mListener.onComplete(result); } else { if (mIsFatal) { mListener.onFatal(mException); } else { mListener.onError(mException); } } runAppCounter(); } } /** * API?? * */ private class ApiException extends Exception { private static final long serialVersionUID = 5337015179934580406L; private int mCode; private String mMessage; public ApiException(int code, String message) { super(); mCode = code; mMessage = message; } public ApiException(StatusLine status, String responseBody) { super(); mCode = status.getStatusCode(); if (responseBody != null && responseBody.length() > 0) { mMessage = responseBody; } else { mMessage = status.getReasonPhrase(); } } @Override public String getMessage() { return mMessage; } public int getCode() { return mCode; } } @Override public void setupAd(Activity activity, AdParameter param) { WebView view = (WebView) activity.findViewById(R.id.webview); // view ?????????? if (view == null) { Log.w(TAG, "webview not found."); return; } view.setWebViewClient(new MapWebViewClient(activity)); if (param == null || param.headerPath == null) { Log.w(TAG, "path is null."); return; } view.loadUrl(param.headerPath); // ????? if (mSelector == Config.APPLICATION) { setupAppCounter(); } } private class AppsCounter { private final HttpUriRequest mMethod; private final CallbackListener mListener; public AppsCounter(final HttpUriRequest method) { mMethod = method; mListener = new CallbackListener() { @Override public void onFatal(ErrorInfo e) { } @Override public void onError(ErrorInfo e) { } @Override public void onComplete(Bundle values) { mAppsCounter = null; } @Override public void onCancel() { } }; } public AppsCounter(final HttpUriRequest method, CallbackListener listener) { mMethod = method; mListener = listener; } public void execute() { new Thread() { public void run() { try { request(mMethod, mListener, false); mListener.onComplete(null); } catch (Exception e) { mListener.onError(null); } } }.start(); } } private class MapWebViewClient extends WebViewClient { private Context mContext; MapWebViewClient(Context context) { super(); this.mContext = context; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // ??? mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); return true; } } }