/*
* Copyright 2014 OpenMarket Ltd
* Copyright 2017 Vector Creations Ltd
* 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 org.matrix.androidsdk;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.squareup.okhttp.OkHttpClient;
import org.matrix.androidsdk.listeners.IMXNetworkEventListener;
import org.matrix.androidsdk.rest.client.MXRestExecutor;
import org.matrix.androidsdk.rest.model.login.Credentials;
import org.matrix.androidsdk.ssl.CertUtil;
import org.matrix.androidsdk.util.JsonUtils;
import org.matrix.androidsdk.util.Log;
import org.matrix.androidsdk.util.UnsentEventsManager;
import java.util.concurrent.TimeUnit;
import retrofit.RequestInterceptor;
import retrofit.RestAdapter;
import retrofit.client.OkClient;
import retrofit.converter.GsonConverter;
/**
* Class for making Matrix API calls.
*/
public class RestClient<T> {
private static final String LOG_TAG = "RestClient";
public static final String URI_API_PREFIX_PATH_R0 = "/_matrix/client/r0";
public static final String URI_API_PREFIX_PATH_UNSTABLE = "/_matrix/client/unstable";
/**
* Prefix used in path of identity server API requests.
*/
public static final String URI_API_PREFIX_IDENTITY = "/_matrix/identity/api/v1";
private static final String PARAM_ACCESS_TOKEN = "access_token";
private static final int CONNECTION_TIMEOUT_MS = 30000;
private static final int READ_TIMEOUT_MS = 60000;
private static final int WRITE_TIMEOUT_MS = 60000;
protected Credentials mCredentials;
protected T mApi;
protected Gson gson;
protected UnsentEventsManager mUnsentEventsManager;
protected HomeserverConnectionConfig mHsConfig;
// unitary tests only
public static boolean mUseMXExececutor = false;
// the user agent
private static String sUserAgent = null;
// http client
private OkHttpClient mOkHttpClient = new OkHttpClient();
public RestClient(HomeserverConnectionConfig hsConfig, Class<T> type, String uriPrefix, boolean withNullSerialization) {
this(hsConfig, type, uriPrefix, withNullSerialization, false);
}
/**
* Public constructor.
* @param hsConfig The homeserver connection config.
*/
public RestClient(HomeserverConnectionConfig hsConfig, Class<T> type, String uriPrefix, boolean withNullSerialization, boolean useIdentityServer) {
// The JSON -> object mapper
gson = JsonUtils.getGson(withNullSerialization);
mHsConfig = hsConfig;
mCredentials = hsConfig.getCredentials();
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
mOkHttpClient.setReadTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS);
mOkHttpClient.setWriteTimeout(WRITE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
try {
mOkHttpClient.setSslSocketFactory(CertUtil.newPinnedSSLSocketFactory(hsConfig));
mOkHttpClient.setHostnameVerifier(CertUtil.newHostnameVerifier(hsConfig));
} catch (Exception e) {
Log.e(LOG_TAG, "## RestClient() setSslSocketFactory failed" + e.getMessage());
}
// remove any trailing http in the uri prefix
if (uriPrefix.startsWith("http://")) {
uriPrefix = uriPrefix.substring("http://".length());
} else if (uriPrefix.startsWith("https://")) {
uriPrefix = uriPrefix.substring("https://".length());
}
final String endPoint = (useIdentityServer ? hsConfig.getIdentityServerUri().toString() : hsConfig.getHomeserverUri().toString()) + uriPrefix;
// Rest adapter for turning API interfaces into actual REST-calling objects
RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint(endPoint)
.setConverter(new GsonConverter(gson))
.setClient(new OkClient(mOkHttpClient))
.setRequestInterceptor(new RequestInterceptor() {
@Override
public void intercept(RequestInterceptor.RequestFacade request) {
if (null != sUserAgent) {
// set a custom user agent
request.addHeader("User-Agent", sUserAgent);
}
// Add the access token to all requests if it is set
if ((mCredentials != null) && (mCredentials.accessToken != null)) {
request.addEncodedQueryParam(PARAM_ACCESS_TOKEN, mCredentials.accessToken);
}
}
});
if (mUseMXExececutor) {
builder.setExecutors(new MXRestExecutor(), new MXRestExecutor());
}
RestAdapter restAdapter = builder.build();
// debug only
//restAdapter.setLogLevel(RestAdapter.LogLevel.FULL);
mApi = restAdapter.create(type);
}
/**
* Create an user agent with the application version.
* @param appContext the application context
*/
public static void initUserAgent(Context appContext) {
String appName = "";
String appVersion = "";
if (null != appContext) {
try {
PackageManager pm = appContext.getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(appContext.getApplicationContext().getPackageName(), 0);
appName = pm.getApplicationLabel(appInfo).toString();
PackageInfo pkgInfo = pm.getPackageInfo(appContext.getApplicationContext().getPackageName(), 0);
appVersion = pkgInfo.versionName;
} catch (Exception e) {
Log.e(LOG_TAG, "## initUserAgent() : failed " + e.getMessage());
}
}
sUserAgent = System.getProperty("http.agent");
// cannot retrieve the application version
if (TextUtils.isEmpty(appName) || TextUtils.isEmpty(appVersion)) {
if (null == sUserAgent) {
sUserAgent = "Java" + System.getProperty("java.version");
}
return;
}
// if there is no user agent or cannot parse it
if ((null == sUserAgent) || (sUserAgent.lastIndexOf(")") == -1) || (sUserAgent.indexOf("(") == -1)) {
sUserAgent = appName + "/" + appVersion + " (MatrixAndroidSDK " + BuildConfig.VERSION_NAME + ")";
} else {
// update
sUserAgent = appName + "/" + appVersion + " " +
sUserAgent.substring(sUserAgent.indexOf("("), sUserAgent.lastIndexOf(")") - 1) +
"; MatrixAndroidSDK " + BuildConfig.VERSION_NAME + ")";
}
}
/**
* Set the unsentEvents manager.
* @param unsentEventsManager The unsentEvents manager.
*/
public void setUnsentEventsManager(UnsentEventsManager unsentEventsManager) {
mUnsentEventsManager = unsentEventsManager;
mUnsentEventsManager.getNetworkConnectivityReceiver().addEventListener(new IMXNetworkEventListener() {
@Override
public void onNetworkConnectionUpdate(boolean isConnected) {
Log.e(LOG_TAG, "## setUnsentEventsManager() : update the requests timeout to " + (isConnected ? CONNECTION_TIMEOUT_MS : 1) + " ms");
mOkHttpClient.setConnectTimeout(isConnected ? CONNECTION_TIMEOUT_MS : 1, TimeUnit.MILLISECONDS);
}
});
}
/**
* Get the user's credentials. Typically for saving them somewhere persistent.
* @return the user credentials
*/
public Credentials getCredentials() {
return mCredentials;
}
/**
* Provide the user's credentials. To be called after login or registration.
* @param credentials the user credentials
*/
public void setCredentials(Credentials credentials) {
mCredentials = credentials;
}
/**
* Default protected constructor for unit tests.
*/
protected RestClient() {
}
/**
* Protected setter for injection by unit tests.
* @param api the api object
*/
protected void setApi(T api) {
mApi = api;
}
}