mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-11-26 22:31:16 +00:00
Replaced deprecated http APIs
This commit is contained in:
parent
b38a152f6a
commit
63e8c22129
21 changed files with 332 additions and 632 deletions
|
@ -28,7 +28,6 @@ dependencies {
|
||||||
compile 'org.mindrot:jbcrypt:0.3m'
|
compile 'org.mindrot:jbcrypt:0.3m'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.robolectric:robolectric:3.1.2'
|
testCompile 'org.robolectric:robolectric:3.1.2'
|
||||||
androidTestCompile 'com.squareup.okhttp:mockwebserver:2.4.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
project.archivesBaseName = 'syncthing'
|
project.archivesBaseName = 'syncthing'
|
||||||
|
|
|
@ -6,12 +6,13 @@ import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MockRestApi extends RestApi {
|
public class MockRestApi extends RestApi {
|
||||||
|
|
||||||
public MockRestApi(Context context, String url, String apiKey,
|
public MockRestApi(Context context, URL url, String apiKey,
|
||||||
OnApiAvailableListener listener) {
|
OnApiAvailableListener listener) {
|
||||||
super(context, url, apiKey, listener, null);
|
super(context, url, apiKey, listener, null);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +77,7 @@ public class MockRestApi extends RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean editFolder(Folder folder, boolean create, Activity activity) {
|
public void editFolder(Folder folder, boolean create, Activity activity) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.os.IBinder;
|
||||||
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
public class MockSyncthingService extends SyncthingService {
|
public class MockSyncthingService extends SyncthingService {
|
||||||
|
@ -67,7 +68,7 @@ public class MockSyncthingService extends SyncthingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWebGuiUrl() {
|
public URL getWebGuiUrl() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
package com.nutomic.syncthingandroid.test.syncthing;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.syncthing.GetTask;
|
|
||||||
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
|
||||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
|
||||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
|
||||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class GetTaskTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
private MockWebServer mServer;
|
|
||||||
|
|
||||||
private static final String RESPONSE = "the response";
|
|
||||||
|
|
||||||
private static final String API_KEY = "the key";
|
|
||||||
|
|
||||||
private static final String PARAM_KEY_ONE = "first-param";
|
|
||||||
|
|
||||||
private static final String PARAM_VALUE_ONE = "first param value";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
mServer = new MockWebServer();
|
|
||||||
mServer.enqueue(new MockResponse().setBody(RESPONSE));
|
|
||||||
mServer.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() throws Exception {
|
|
||||||
super.tearDown();
|
|
||||||
|
|
||||||
// TODO: causes problems, see https://github.com/square/okhttp/issues/1033
|
|
||||||
//mServer.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetNoParams() throws IOException, InterruptedException {
|
|
||||||
new GetTask("") {
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(String s) {
|
|
||||||
assertEquals(RESPONSE, s);
|
|
||||||
}
|
|
||||||
}.execute(mServer.getUrl("").toString(), GetTask.URI_CONFIG, API_KEY);
|
|
||||||
RecordedRequest request = mServer.takeRequest();
|
|
||||||
assertEquals(API_KEY, request.getHeader(RestApi.HEADER_API_KEY));
|
|
||||||
Uri uri = Uri.parse(request.getPath());
|
|
||||||
assertEquals(GetTask.URI_CONFIG, uri.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetParams() throws IOException, InterruptedException {
|
|
||||||
new GetTask("") {
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(String s) {
|
|
||||||
assertEquals(RESPONSE, s);
|
|
||||||
}
|
|
||||||
}.execute(mServer.getUrl("").toString(), GetTask.URI_CONFIG, API_KEY, PARAM_KEY_ONE,
|
|
||||||
PARAM_VALUE_ONE);
|
|
||||||
RecordedRequest request = mServer.takeRequest();
|
|
||||||
assertEquals(API_KEY, request.getHeader(RestApi.HEADER_API_KEY));
|
|
||||||
Uri uri = Uri.parse(request.getPath());
|
|
||||||
assertEquals(GetTask.URI_CONFIG, uri.getPath());
|
|
||||||
assertEquals(PARAM_VALUE_ONE, uri.getQueryParameter(PARAM_KEY_ONE));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package com.nutomic.syncthingandroid.test.syncthing;
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.syncthing.PollWebGuiAvailableTask;
|
|
||||||
import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable;
|
|
||||||
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
|
||||||
import com.nutomic.syncthingandroid.test.MockContext;
|
|
||||||
import com.nutomic.syncthingandroid.util.ConfigXml;
|
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class PollWebGuiAvailableTaskTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
private ConfigXml mConfig;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
mConfig = new ConfigXml(new MockContext(getContext()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() throws Exception {
|
|
||||||
super.tearDown();
|
|
||||||
|
|
||||||
ConfigXml.getConfigFile(new MockContext(getContext())).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPolling() throws InterruptedException {
|
|
||||||
new SyncthingRunnable(new MockContext(getContext()), SyncthingRunnable.Command.main);
|
|
||||||
|
|
||||||
String httpsCertPath = getContext().getFilesDir() + "/" + SyncthingService.HTTPS_CERT_FILE;
|
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
new PollWebGuiAvailableTask(httpsCertPath) {
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void aVoid) {
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
}.execute(mConfig.getWebGuiUrl());
|
|
||||||
latch.await(1, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
// TODO: Unit tests fail when Syncthing is killed SyncthingRunnable.killSyncthing();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ package com.nutomic.syncthingandroid.test.syncthing;
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.syncthing.PollWebGuiAvailableTask;
|
import com.nutomic.syncthingandroid.http.PollWebGuiAvailableTask;
|
||||||
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable;
|
import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable;
|
||||||
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||||
|
@ -28,13 +28,13 @@ public class RestApiTest extends AndroidTestCase {
|
||||||
String httpsCertPath = getContext().getFilesDir() + "/" + SyncthingService.HTTPS_CERT_FILE;
|
String httpsCertPath = getContext().getFilesDir() + "/" + SyncthingService.HTTPS_CERT_FILE;
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(2);
|
final CountDownLatch latch = new CountDownLatch(2);
|
||||||
new PollWebGuiAvailableTask(httpsCertPath) {
|
new PollWebGuiAvailableTask(config.getWebGuiUrl(), httpsCertPath, config.getApiKey()) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Void aVoid) {
|
protected void onPostExecute(Void aVoid) {
|
||||||
mApi.onWebGuiAvailable();
|
mApi.onWebGuiAvailable();
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
}.execute(config.getWebGuiUrl());
|
}.execute();
|
||||||
mApi = new RestApi(getContext(), config.getWebGuiUrl(), config.getApiKey(),
|
mApi = new RestApi(getContext(), config.getWebGuiUrl(), config.getApiKey(),
|
||||||
latch::countDown, null);
|
latch::countDown, null);
|
||||||
latch.await(1, TimeUnit.SECONDS);
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class ConfigXmlTest extends AndroidTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetWebGuiUrl() {
|
public void testGetWebGuiUrl() {
|
||||||
assertTrue(mConfig.getWebGuiUrl().startsWith("https://127.0.0.1:"));
|
assertTrue(mConfig.getWebGuiUrl().toString().startsWith("https://127.0.0.1:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ public class WebGuiActivity extends SyncthingActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebGuiAvailable() {
|
public void onWebGuiAvailable() {
|
||||||
mWebView.loadUrl(getService().getWebGuiUrl());
|
mWebView.loadUrl(getService().getWebGuiUrl().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
57
src/main/java/com/nutomic/syncthingandroid/http/GetTask.java
Normal file
57
src/main/java/com/nutomic/syncthingandroid/http/GetTask.java
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package com.nutomic.syncthingandroid.http;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a GET request to the Syncthing API
|
||||||
|
* Performs a GET request with no parameters to the URL in uri[0] with the path in uri[1] and
|
||||||
|
* returns the result as a String.
|
||||||
|
*/
|
||||||
|
public class GetTask extends RestTask<String, Void, String> {
|
||||||
|
|
||||||
|
private static final String TAG = "GetTask";
|
||||||
|
|
||||||
|
public static final String URI_CONFIG = "/rest/system/config";
|
||||||
|
public static final String URI_VERSION = "/rest/system/version";
|
||||||
|
public static final String URI_SYSTEM = "/rest/system/status";
|
||||||
|
public static final String URI_CONNECTIONS = "/rest/system/connections";
|
||||||
|
public static final String URI_MODEL = "/rest/db/status";
|
||||||
|
public static final String URI_DEVICEID = "/rest/svc/deviceid";
|
||||||
|
public static final String URI_REPORT = "/rest/svc/report";
|
||||||
|
public static final String URI_EVENTS = "/rest/events";
|
||||||
|
|
||||||
|
public GetTask(URL url, String path, String httpsCertPath, String apiKey) {
|
||||||
|
super(url, path, httpsCertPath, apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param params Keys and values for the query string.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(String... params) {
|
||||||
|
try {
|
||||||
|
HttpsURLConnection connection = openConnection(params);
|
||||||
|
Log.v(TAG, "Calling Rest API at " + connection.getURL());
|
||||||
|
connection.connect();
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
|
||||||
|
String line;
|
||||||
|
String result = "";
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
result += line;
|
||||||
|
}
|
||||||
|
br.close();
|
||||||
|
Log.v(TAG, "API call result: " + result);
|
||||||
|
return result;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to call rest api", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.nutomic.syncthingandroid.http;
|
||||||
|
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls to load the web interface, until we receive http status 200.
|
||||||
|
*/
|
||||||
|
public abstract class PollWebGuiAvailableTask extends RestTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private static final String TAG = "PollWebGuiAvailableTask";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval in ms, at which connections to the web gui are performed on first start
|
||||||
|
* to find out if it's online.
|
||||||
|
*/
|
||||||
|
private static final long WEB_GUI_POLL_INTERVAL = 100;
|
||||||
|
|
||||||
|
public PollWebGuiAvailableTask(URL url, String httpsCertPath, String apiKey) {
|
||||||
|
super(url, "", httpsCertPath, apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... aVoid) {
|
||||||
|
int status = 0;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
HttpsURLConnection connection = openConnection();
|
||||||
|
connection.connect();
|
||||||
|
status = connection.getResponseCode();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// We catch this in every call, as long as the service is not online, so we ignore and continue.
|
||||||
|
try {
|
||||||
|
Thread.sleep(WEB_GUI_POLL_INTERVAL);
|
||||||
|
} catch (InterruptedException e2) {
|
||||||
|
Log.w(TAG, "Failed to sleep", e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (status != HttpsURLConnection.HTTP_OK);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.nutomic.syncthingandroid.http;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a POST request to the Syncthing API.
|
||||||
|
*/
|
||||||
|
public class PostTask extends RestTask<String, Void, Boolean> {
|
||||||
|
|
||||||
|
private static final String TAG = "PostTask";
|
||||||
|
|
||||||
|
public static final String URI_CONFIG = "/rest/system/config";
|
||||||
|
public static final String URI_SCAN = "/rest/db/scan";
|
||||||
|
|
||||||
|
public PostTask(URL url, String path, String httpsCertPath, String apiKey) {
|
||||||
|
super(url, path, httpsCertPath, apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For {@link #URI_CONFIG}, params[0] must contain the config.
|
||||||
|
*
|
||||||
|
* For {@link #URI_SCAN}, params[0] must contain the folder, and params[1] the subfolder.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(String... params) {
|
||||||
|
try {
|
||||||
|
HttpsURLConnection connection = (mPath.equals(URI_SCAN))
|
||||||
|
? openConnection("folder", params[0], "sub", params[1])
|
||||||
|
: openConnection();
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
Log.v(TAG, "Calling Rest API at " + connection.getURL());
|
||||||
|
|
||||||
|
if (mPath.equals(URI_CONFIG)) {
|
||||||
|
OutputStream os = connection.getOutputStream();
|
||||||
|
os.write(params[0].getBytes("UTF-8"));
|
||||||
|
}
|
||||||
|
connection.connect();
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
|
||||||
|
String line;
|
||||||
|
String result = "";
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
result += line;
|
||||||
|
}
|
||||||
|
br.close();
|
||||||
|
Log.v(TAG, "API call result: " + result);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to call rest api", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
111
src/main/java/com/nutomic/syncthingandroid/http/RestTask.java
Normal file
111
src/main/java/com/nutomic/syncthingandroid/http/RestTask.java
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package com.nutomic.syncthingandroid.http;
|
||||||
|
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
|
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
public abstract class RestTask<A, B, C> extends AsyncTask<A, B, C> {
|
||||||
|
|
||||||
|
private static final String TAG = "RestTask";
|
||||||
|
|
||||||
|
private final URL mUrl;
|
||||||
|
protected final String mPath;
|
||||||
|
private final String mHttpsCertPath;
|
||||||
|
private final String mApiKey;
|
||||||
|
|
||||||
|
public RestTask(URL url, String path, String httpsCertPath, String apiKey) {
|
||||||
|
mUrl = url;
|
||||||
|
mPath = path;
|
||||||
|
mHttpsCertPath = httpsCertPath;
|
||||||
|
mApiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpsURLConnection openConnection(String... params) throws IOException {
|
||||||
|
Uri.Builder uriBuilder = Uri.parse(mUrl.toString())
|
||||||
|
.buildUpon()
|
||||||
|
.path(mPath);
|
||||||
|
for (int paramCounter = 0; paramCounter + 1 < params.length; ) {
|
||||||
|
uriBuilder.appendQueryParameter(params[paramCounter++], params[paramCounter++]);
|
||||||
|
}
|
||||||
|
URL url = new URL(uriBuilder.build().toString());
|
||||||
|
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setRequestProperty(RestApi.HEADER_API_KEY, mApiKey);
|
||||||
|
connection.setHostnameVerifier((h, s) -> true);
|
||||||
|
connection.setSSLSocketFactory(getSslSocketFactory());
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLSocketFactory getSslSocketFactory() {
|
||||||
|
try {
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
sslContext.init(null, new TrustManager[]{new SyncthingTrustManager()},
|
||||||
|
new SecureRandom());
|
||||||
|
return sslContext.getSocketFactory();
|
||||||
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TrustManager checking against the local Syncthing instance's https public key.
|
||||||
|
*
|
||||||
|
* Based on http://stackoverflow.com/questions/16719959#16759793
|
||||||
|
*/
|
||||||
|
private class SyncthingTrustManager implements X509TrustManager {
|
||||||
|
|
||||||
|
private static final String TAG = "SyncthingTrustManager";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||||
|
throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies certs against public key of the local syncthing instance
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] certs,
|
||||||
|
String authType) throws CertificateException {
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
is = new FileInputStream(mHttpsCertPath);
|
||||||
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
X509Certificate ca = (X509Certificate) cf.generateCertificate(is);
|
||||||
|
for (X509Certificate cert : certs) {
|
||||||
|
cert.verify(ca.getPublicKey());
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException | NoSuchAlgorithmException | InvalidKeyException |
|
||||||
|
NoSuchProviderException | SignatureException e) {
|
||||||
|
throw new CertificateException("Untrusted Certificate!", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (is != null)
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
package com.nutomic.syncthingandroid.syncthing;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.util.Https;
|
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.NameValuePair;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.client.utils.URLEncodedUtils;
|
|
||||||
import org.apache.http.message.BasicHeader;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
import org.apache.http.protocol.HTTP;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a GET request with no parameters to the URL in uri[0] with the path in uri[1] and
|
|
||||||
* returns the result as a String.
|
|
||||||
*/
|
|
||||||
public class GetTask extends AsyncTask<String, Void, String> {
|
|
||||||
|
|
||||||
private static final String TAG = "GetTask";
|
|
||||||
|
|
||||||
public static final String URI_CONFIG = "/rest/system/config";
|
|
||||||
public static final String URI_VERSION = "/rest/system/version";
|
|
||||||
public static final String URI_SYSTEM = "/rest/system/status";
|
|
||||||
public static final String URI_CONNECTIONS = "/rest/system/connections";
|
|
||||||
public static final String URI_MODEL = "/rest/db/status";
|
|
||||||
public static final String URI_DEVICEID = "/rest/svc/deviceid";
|
|
||||||
public static final String URI_REPORT = "/rest/svc/report";
|
|
||||||
public static final String URI_EVENTS = "/rest/events";
|
|
||||||
|
|
||||||
private final String mHttpsCertPath;
|
|
||||||
|
|
||||||
public GetTask(String httpsCertPath) {
|
|
||||||
mHttpsCertPath = httpsCertPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* params[0] Syncthing hostname
|
|
||||||
* params[1] URI to call
|
|
||||||
* params[2] Syncthing API key
|
|
||||||
* params[3] optional parameter key
|
|
||||||
* params[4] optional parameter value
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected String doInBackground(String... params) {
|
|
||||||
String fullUri = params[0] + params[1];
|
|
||||||
Log.v(TAG, "Calling Rest API at " + fullUri);
|
|
||||||
|
|
||||||
if (params.length >= 5) {
|
|
||||||
LinkedList<NameValuePair> urlParams = new LinkedList<>();
|
|
||||||
for (int paramCounter = 3; paramCounter + 1 < params.length; ) {
|
|
||||||
urlParams.add(new BasicNameValuePair(params[paramCounter++], params[paramCounter++]));
|
|
||||||
}
|
|
||||||
fullUri += "?" + URLEncodedUtils.format(urlParams, HTTP.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry at most 5 times before failing
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
HttpClient httpclient = Https.createHttpsClient(mHttpsCertPath);
|
|
||||||
HttpGet get = new HttpGet(fullUri);
|
|
||||||
get.addHeader(new BasicHeader(RestApi.HEADER_API_KEY, params[2]));
|
|
||||||
|
|
||||||
if (isCancelled())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
HttpResponse response = httpclient.execute(get);
|
|
||||||
HttpEntity entity = response.getEntity();
|
|
||||||
|
|
||||||
if (entity != null) {
|
|
||||||
InputStream is = entity.getContent();
|
|
||||||
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
|
|
||||||
String line;
|
|
||||||
String result = "";
|
|
||||||
while ((line = br.readLine()) != null) {
|
|
||||||
result += line;
|
|
||||||
}
|
|
||||||
br.close();
|
|
||||||
Log.v(TAG, "API call result: " + result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} catch (IOException|IllegalArgumentException e) {
|
|
||||||
Log.w(TAG, "Failed to call Rest API at " + fullUri);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Don't push the API too hard
|
|
||||||
Thread.sleep(500 * i);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
Log.w(TAG, "Retrying GetTask Rest API call (" + (i + 1) + "/5)");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package com.nutomic.syncthingandroid.syncthing;
|
|
||||||
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.util.Https;
|
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.HttpStatus;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.methods.HttpHead;
|
|
||||||
import org.apache.http.conn.HttpHostConnectException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Polls SYNCTHING_URL until it returns HTTP status OK, then calls all listeners
|
|
||||||
* in mOnWebGuiAvailableListeners and clears it.
|
|
||||||
*/
|
|
||||||
public abstract class PollWebGuiAvailableTask extends AsyncTask<String, Void, Void> {
|
|
||||||
|
|
||||||
private static final String TAG = "PollWebGuiAvailableTask";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interval in ms, at which connections to the web gui are performed on first start
|
|
||||||
* to find out if it's online.
|
|
||||||
*/
|
|
||||||
private static final long WEB_GUI_POLL_INTERVAL = 100;
|
|
||||||
|
|
||||||
private final String mHttpsCertPath;
|
|
||||||
|
|
||||||
public PollWebGuiAvailableTask(String httpsCertPath) {
|
|
||||||
mHttpsCertPath = httpsCertPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param url The URL of the web GUI (eg 127.0.0.1:8384).
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(String... url) {
|
|
||||||
int status = 0;
|
|
||||||
HttpClient httpclient = Https.createHttpsClient(mHttpsCertPath);
|
|
||||||
HttpHead head = new HttpHead(url[0]);
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
Thread.sleep(WEB_GUI_POLL_INTERVAL);
|
|
||||||
HttpResponse response = httpclient.execute(head);
|
|
||||||
// NOTE: status is not really needed, as HttpHostConnectException is thrown
|
|
||||||
// earlier.
|
|
||||||
status = response.getStatusLine().getStatusCode();
|
|
||||||
} catch (HttpHostConnectException e) {
|
|
||||||
// We catch this in every call, as long as the service is not online,
|
|
||||||
// so we ignore and continue.
|
|
||||||
} catch (IOException|InterruptedException|IllegalArgumentException e) {
|
|
||||||
//Log.w(TAG, "Failed to poll for web interface", e);
|
|
||||||
}
|
|
||||||
} while (status != HttpStatus.SC_OK && status != HttpStatus.SC_UNAUTHORIZED);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
package com.nutomic.syncthingandroid.syncthing;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.util.Https;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.entity.StringEntity;
|
|
||||||
import org.apache.http.message.BasicHeader;
|
|
||||||
import org.apache.http.protocol.HTTP;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a new config to {@link #URI_CONFIG}.
|
|
||||||
*/
|
|
||||||
public class PostConfigTask extends AsyncTask<String, Void, Boolean> {
|
|
||||||
|
|
||||||
private static final String TAG = "PostConfigTask";
|
|
||||||
|
|
||||||
public static final String URI_CONFIG = "/rest/system/config";
|
|
||||||
|
|
||||||
private final String mHttpsCertPath;
|
|
||||||
|
|
||||||
public PostConfigTask(String httpsCertPath) {
|
|
||||||
mHttpsCertPath = httpsCertPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* params[0] Syncthing hostname
|
|
||||||
* params[1] Syncthing API key
|
|
||||||
* params[2] The new config
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(String... params) {
|
|
||||||
String fullUri = params[0] + URI_CONFIG;
|
|
||||||
Log.v(TAG, "Calling Rest API at " + fullUri);
|
|
||||||
|
|
||||||
HttpClient httpclient = Https.createHttpsClient(mHttpsCertPath);
|
|
||||||
HttpPost post = new HttpPost(fullUri);
|
|
||||||
post.addHeader(new BasicHeader(RestApi.HEADER_API_KEY, params[1]));
|
|
||||||
|
|
||||||
try {
|
|
||||||
post.setEntity(new StringEntity(params[2], HTTP.UTF_8));
|
|
||||||
Log.v(TAG, "API call parameters: " + params[2]);
|
|
||||||
httpclient.execute(post);
|
|
||||||
} catch (IOException|IllegalArgumentException e) {
|
|
||||||
Log.w(TAG, "Failed to call Rest API at " + fullUri, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
package com.nutomic.syncthingandroid.syncthing;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.util.Https;
|
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.NameValuePair;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.client.utils.URLEncodedUtils;
|
|
||||||
import org.apache.http.message.BasicHeader;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
import org.apache.http.protocol.HTTP;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a POST request to {@link #URI_SCAN} to notify Syncthing of a changed file or folder.
|
|
||||||
*/
|
|
||||||
public class PostScanTask extends AsyncTask<String, Void, Void> {
|
|
||||||
|
|
||||||
private static final String TAG = "PostScanTask";
|
|
||||||
|
|
||||||
public static final String URI_SCAN = "/rest/db/scan";
|
|
||||||
|
|
||||||
private final String mHttpsCertPath;
|
|
||||||
|
|
||||||
public PostScanTask(String httpsCertPath) {
|
|
||||||
mHttpsCertPath = httpsCertPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* params[0] Syncthing hostname
|
|
||||||
* params[1] Syncthing API key
|
|
||||||
* params[2] folder parameter (the Syncthing folder to update)
|
|
||||||
* params[3] sub parameter (the subfolder to update
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(String... params) {
|
|
||||||
String fullUri = params[0] + URI_SCAN;
|
|
||||||
|
|
||||||
LinkedList<NameValuePair> urlParams = new LinkedList<>();
|
|
||||||
urlParams.add(new BasicNameValuePair("folder", params[2]));
|
|
||||||
urlParams.add(new BasicNameValuePair("sub", params[3]));
|
|
||||||
fullUri += "?" + URLEncodedUtils.format(urlParams, HTTP.UTF_8);
|
|
||||||
Log.v(TAG, "Calling Rest API at " + fullUri);
|
|
||||||
|
|
||||||
// Retry at most 5 times before failing
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
HttpClient httpclient = Https.createHttpsClient(mHttpsCertPath);
|
|
||||||
HttpPost post = new HttpPost(fullUri);
|
|
||||||
post.addHeader(new BasicHeader(RestApi.HEADER_API_KEY, params[1]));
|
|
||||||
|
|
||||||
if (isCancelled())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
HttpResponse response = httpclient.execute(post);
|
|
||||||
if (response.getEntity() != null)
|
|
||||||
return null;
|
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
|
||||||
Log.w(TAG, "Failed to call Rest API at " + fullUri);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Don't push the API too hard
|
|
||||||
Thread.sleep(500 * i);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
Log.w(TAG, "Retrying GetTask Rest API call (" + (i + 1) + "/5)");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -11,26 +11,23 @@ import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import com.nutomic.syncthingandroid.BuildConfig;
|
import com.nutomic.syncthingandroid.BuildConfig;
|
||||||
import com.nutomic.syncthingandroid.R;
|
import com.nutomic.syncthingandroid.R;
|
||||||
import com.nutomic.syncthingandroid.activities.RestartActivity;
|
import com.nutomic.syncthingandroid.activities.RestartActivity;
|
||||||
|
import com.nutomic.syncthingandroid.http.GetTask;
|
||||||
|
import com.nutomic.syncthingandroid.http.PostTask;
|
||||||
import com.nutomic.syncthingandroid.util.FolderObserver;
|
import com.nutomic.syncthingandroid.util.FolderObserver;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.net.URL;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,7 +166,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
|
|
||||||
private String mVersion;
|
private String mVersion;
|
||||||
|
|
||||||
private final String mUrl;
|
private final URL mUrl;
|
||||||
|
|
||||||
private final String mApiKey;
|
private final String mApiKey;
|
||||||
|
|
||||||
|
@ -203,7 +200,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
*/
|
*/
|
||||||
private final Map<String, String> mCacheFolderPathLookup = new HashMap<>();
|
private final Map<String, String> mCacheFolderPathLookup = new HashMap<>();
|
||||||
|
|
||||||
public RestApi(Context context, String url, String apiKey, OnApiAvailableListener apiListener,
|
public RestApi(Context context, URL url, String apiKey, OnApiAvailableListener apiListener,
|
||||||
OnConfigChangedListener configListener) {
|
OnConfigChangedListener configListener) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mUrl = url;
|
mUrl = url;
|
||||||
|
@ -237,7 +234,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
@Override
|
@Override
|
||||||
public void onWebGuiAvailable() {
|
public void onWebGuiAvailable() {
|
||||||
mAvailableCount.set(0);
|
mAvailableCount.set(0);
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_VERSION, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String s) {
|
protected void onPostExecute(String s) {
|
||||||
if (s == null)
|
if (s == null)
|
||||||
|
@ -253,8 +250,8 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
Log.w(TAG, "Failed to parse config", e);
|
Log.w(TAG, "Failed to parse config", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mUrl, GetTask.URI_VERSION, mApiKey);
|
}.execute();
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_CONFIG, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String config) {
|
protected void onPostExecute(String config) {
|
||||||
try {
|
try {
|
||||||
|
@ -264,7 +261,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
Log.w(TAG, "Failed to parse config", e);
|
Log.w(TAG, "Failed to parse config", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mUrl, GetTask.URI_CONFIG, mApiKey);
|
}.execute();
|
||||||
getSystemInfo(info -> {
|
getSystemInfo(info -> {
|
||||||
mLocalDeviceId = info.myID;
|
mLocalDeviceId = info.myID;
|
||||||
tryIsAvailable();
|
tryIsAvailable();
|
||||||
|
@ -367,7 +364,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
*/
|
*/
|
||||||
public void requireRestart(Activity activity) {
|
public void requireRestart(Activity activity) {
|
||||||
if (mRestartPostponed) {
|
if (mRestartPostponed) {
|
||||||
new PostConfigTask(mHttpsCertPath).execute(mUrl, mApiKey, mConfig.toString());
|
new PostTask(mUrl, PostTask.URI_CONFIG, mHttpsCertPath, mApiKey).execute(mConfig.toString());
|
||||||
} else {
|
} else {
|
||||||
activity.startActivity(new Intent(mContext, RestartActivity.class));
|
activity.startActivity(new Intent(mContext, RestartActivity.class));
|
||||||
}
|
}
|
||||||
|
@ -380,13 +377,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
* This executes a restart immediately, and does not show a dialog.
|
* This executes a restart immediately, and does not show a dialog.
|
||||||
*/
|
*/
|
||||||
public void updateConfig() {
|
public void updateConfig() {
|
||||||
new PostConfigTask(mHttpsCertPath) {
|
new PostTask(mUrl, PostTask.URI_CONFIG, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean aBoolean) {
|
protected void onPostExecute(Boolean b) {
|
||||||
mContext.startService(new Intent(mContext, SyncthingService.class)
|
mContext.startService(new Intent(mContext, SyncthingService.class)
|
||||||
.setAction(SyncthingService.ACTION_RESTART));
|
.setAction(SyncthingService.ACTION_RESTART));
|
||||||
}
|
}
|
||||||
}.execute(mUrl, mApiKey, mConfig.toString());
|
}.execute(mConfig.toString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,7 +442,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
* @param listener Callback invoked when the result is received.
|
* @param listener Callback invoked when the result is received.
|
||||||
*/
|
*/
|
||||||
public void getSystemInfo(final OnReceiveSystemInfoListener listener) {
|
public void getSystemInfo(final OnReceiveSystemInfoListener listener) {
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_SYSTEM, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String s) {
|
protected void onPostExecute(String s) {
|
||||||
if (s == null)
|
if (s == null)
|
||||||
|
@ -472,7 +469,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
Log.w(TAG, "Failed to read system info", e);
|
Log.w(TAG, "Failed to read system info", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mUrl, GetTask.URI_SYSTEM, mApiKey);
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -481,7 +478,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
* @param listener Callback invoked when the result is received.
|
* @param listener Callback invoked when the result is received.
|
||||||
*/
|
*/
|
||||||
public void getSystemVersion(final OnReceiveSystemVersionListener listener) {
|
public void getSystemVersion(final OnReceiveSystemVersionListener listener) {
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_VERSION, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String response) {
|
protected void onPostExecute(String response) {
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
|
@ -495,7 +492,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
Log.w(TAG, "Failed to read system info", e);
|
Log.w(TAG, "Failed to read system info", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mUrl, GetTask.URI_VERSION, mApiKey);
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -594,7 +591,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
* Use the key {@link #TOTAL_STATS} to get connection info for the local device.
|
* Use the key {@link #TOTAL_STATS} to get connection info for the local device.
|
||||||
*/
|
*/
|
||||||
public void getConnections(final OnReceiveConnectionsListener listener) {
|
public void getConnections(final OnReceiveConnectionsListener listener) {
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_CONNECTIONS, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String s) {
|
protected void onPostExecute(String s) {
|
||||||
if (s == null)
|
if (s == null)
|
||||||
|
@ -650,7 +647,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
Log.w(TAG, "Failed to parse connections", e);
|
Log.w(TAG, "Failed to parse connections", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mUrl, GetTask.URI_CONNECTIONS, mApiKey);
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -720,7 +717,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
* Returns status information about the folder with the given id.
|
* Returns status information about the folder with the given id.
|
||||||
*/
|
*/
|
||||||
public void getModel(final String folderId, final OnReceiveModelListener listener) {
|
public void getModel(final String folderId, final OnReceiveModelListener listener) {
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_MODEL, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String s) {
|
protected void onPostExecute(String s) {
|
||||||
if (s == null)
|
if (s == null)
|
||||||
|
@ -748,7 +745,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
Log.w(TAG, "Failed to read folder info", e);
|
Log.w(TAG, "Failed to read folder info", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mUrl, GetTask.URI_MODEL, mApiKey, "folder", folderId);
|
}.execute("folder", folderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -779,7 +776,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
* The OnReceiveEventListeners onEvent method is called for each event.
|
* The OnReceiveEventListeners onEvent method is called for each event.
|
||||||
*/
|
*/
|
||||||
public final void getEvents(final long sinceId, final long limit, final OnReceiveEventListener listener) {
|
public final void getEvents(final long sinceId, final long limit, final OnReceiveEventListener listener) {
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_EVENTS, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String s) {
|
protected void onPostExecute(String s) {
|
||||||
if (s == null)
|
if (s == null)
|
||||||
|
@ -815,7 +812,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
Log.w(TAG, "Failed to read events", e);
|
Log.w(TAG, "Failed to read events", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, GetTask.URI_EVENTS, mApiKey,
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||||
"since", String.valueOf(sinceId), "limit", String.valueOf(limit));
|
"since", String.valueOf(sinceId), "limit", String.valueOf(limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -998,7 +995,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
* Normalizes a given device ID.
|
* Normalizes a given device ID.
|
||||||
*/
|
*/
|
||||||
public void normalizeDeviceId(final String id, final OnDeviceIdNormalizedListener listener) {
|
public void normalizeDeviceId(final String id, final OnDeviceIdNormalizedListener listener) {
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_DEVICEID, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String s) {
|
protected void onPostExecute(String s) {
|
||||||
super.onPostExecute(s);
|
super.onPostExecute(s);
|
||||||
|
@ -1016,7 +1013,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
}
|
}
|
||||||
listener.onDeviceIdNormalized(normalized, error);
|
listener.onDeviceIdNormalized(normalized, error);
|
||||||
}
|
}
|
||||||
}.execute(mUrl, GetTask.URI_DEVICEID, mApiKey, "id", id);
|
}.execute("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1050,7 +1047,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onFolderFileChange(String folderId, String relativePath) {
|
public void onFolderFileChange(String folderId, String relativePath) {
|
||||||
new PostScanTask(mHttpsCertPath).execute(mUrl, mApiKey, folderId, relativePath);
|
new PostTask(mUrl, PostTask.URI_SCAN, mHttpsCertPath, mApiKey).execute(folderId, relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1109,7 +1106,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
* Returns prettyfied usage report.
|
* Returns prettyfied usage report.
|
||||||
*/
|
*/
|
||||||
public void getUsageReport(final OnReceiveUsageReportListener listener) {
|
public void getUsageReport(final OnReceiveUsageReportListener listener) {
|
||||||
new GetTask(mHttpsCertPath) {
|
new GetTask(mUrl, GetTask.URI_REPORT, mHttpsCertPath, mApiKey) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String s) {
|
protected void onPostExecute(String s) {
|
||||||
try {
|
try {
|
||||||
|
@ -1121,7 +1118,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
throw new RuntimeException("Failed to prettify usage report", e);
|
throw new RuntimeException("Failed to prettify usage report", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mUrl, GetTask.URI_REPORT, mApiKey);
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.R;
|
import com.nutomic.syncthingandroid.R;
|
||||||
import com.nutomic.syncthingandroid.activities.MainActivity;
|
import com.nutomic.syncthingandroid.activities.MainActivity;
|
||||||
|
import com.nutomic.syncthingandroid.http.PollWebGuiAvailableTask;
|
||||||
import com.nutomic.syncthingandroid.util.ConfigXml;
|
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||||
import com.nutomic.syncthingandroid.util.FolderObserver;
|
import com.nutomic.syncthingandroid.util.FolderObserver;
|
||||||
import com.nutomic.syncthingandroid.util.PRNGFixes;
|
import com.nutomic.syncthingandroid.util.PRNGFixes;
|
||||||
|
@ -30,6 +31,7 @@ import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -226,8 +228,8 @@ public class SyncthingService extends Service implements
|
||||||
registerOnWebGuiAvailableListener(mApi);
|
registerOnWebGuiAvailableListener(mApi);
|
||||||
if (mEventProcessor != null)
|
if (mEventProcessor != null)
|
||||||
registerOnWebGuiAvailableListener(mEventProcessor);
|
registerOnWebGuiAvailableListener(mEventProcessor);
|
||||||
new PollWebGuiAvailableTaskImpl(getFilesDir() + "/" + HTTPS_CERT_FILE)
|
new PollWebGuiAvailableTaskImpl(getWebGuiUrl(), getFilesDir() + "/" + HTTPS_CERT_FILE, mConfig.getApiKey())
|
||||||
.execute(mConfig.getWebGuiUrl());
|
.execute();
|
||||||
mRunnable = new SyncthingRunnable(this, SyncthingRunnable.Command.main);
|
mRunnable = new SyncthingRunnable(this, SyncthingRunnable.Command.main);
|
||||||
new Thread(mRunnable).start();
|
new Thread(mRunnable).start();
|
||||||
updateNotification();
|
updateNotification();
|
||||||
|
@ -321,10 +323,10 @@ public class SyncthingService extends Service implements
|
||||||
* version, and reads syncthing URL and API key (these are passed internally as
|
* version, and reads syncthing URL and API key (these are passed internally as
|
||||||
* {@code Pair<String, String>}.
|
* {@code Pair<String, String>}.
|
||||||
*/
|
*/
|
||||||
private class StartupTask extends AsyncTask<Void, Void, Pair<String, String>> {
|
private class StartupTask extends AsyncTask<Void, Void, Pair<URL, String>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Pair<String, String> doInBackground(Void... voids) {
|
protected Pair<URL, String> doInBackground(Void... voids) {
|
||||||
try {
|
try {
|
||||||
mConfig = new ConfigXml(SyncthingService.this);
|
mConfig = new ConfigXml(SyncthingService.this);
|
||||||
return new Pair<>(mConfig.getWebGuiUrl(), mConfig.getApiKey());
|
return new Pair<>(mConfig.getWebGuiUrl(), mConfig.getApiKey());
|
||||||
|
@ -334,7 +336,7 @@ public class SyncthingService extends Service implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Pair<String, String> urlAndKey) {
|
protected void onPostExecute(Pair<URL, String> urlAndKey) {
|
||||||
if (urlAndKey == null) {
|
if (urlAndKey == null) {
|
||||||
Toast.makeText(SyncthingService.this, R.string.config_create_failed,
|
Toast.makeText(SyncthingService.this, R.string.config_create_failed,
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
|
@ -478,8 +480,8 @@ public class SyncthingService extends Service implements
|
||||||
|
|
||||||
private class PollWebGuiAvailableTaskImpl extends PollWebGuiAvailableTask {
|
private class PollWebGuiAvailableTaskImpl extends PollWebGuiAvailableTask {
|
||||||
|
|
||||||
public PollWebGuiAvailableTaskImpl(String httpsCertPath) {
|
public PollWebGuiAvailableTaskImpl(URL url, String httpsCertPath, String apiKey) {
|
||||||
super(httpsCertPath);
|
super(url, httpsCertPath, apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -528,7 +530,7 @@ public class SyncthingService extends Service implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getWebGuiUrl() {
|
public URL getWebGuiUrl() {
|
||||||
return mConfig.getWebGuiUrl();
|
return mConfig.getWebGuiUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ import org.xml.sax.SAXException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -96,8 +98,12 @@ public class ConfigXml {
|
||||||
return new File(context.getFilesDir(), CONFIG_FILE);
|
return new File(context.getFilesDir(), CONFIG_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getWebGuiUrl() {
|
public URL getWebGuiUrl() {
|
||||||
return "https://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent();
|
try {
|
||||||
|
return new URL("https://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent());
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException("Failed to parse web interface URL", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getApiKey() {
|
public String getApiKey() {
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
package com.nutomic.syncthingandroid.util;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.security.UnrecoverableKeyException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TrustManager allowing the Syncthing https.pem CA
|
|
||||||
*
|
|
||||||
* Based on http://stackoverflow.com/questions/16719959#16759793
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class CustomX509TrustManager implements X509TrustManager {
|
|
||||||
|
|
||||||
private static final String TAG = "CustomX509TrustManager";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Taken from: http://janis.peisenieks.lv/en/76/english-making-an-ssl-connection-via-android/
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class CustomSSLSocketFactory extends SSLSocketFactory {
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
||||||
|
|
||||||
public CustomSSLSocketFactory(SSLContext context)
|
|
||||||
throws KeyManagementException, NoSuchAlgorithmException,
|
|
||||||
KeyStoreException, UnrecoverableKeyException {
|
|
||||||
super(null);
|
|
||||||
sslContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(Socket socket, String host, int port,
|
|
||||||
boolean autoClose) throws IOException {
|
|
||||||
return sslContext.getSocketFactory().createSocket(socket, host, port,
|
|
||||||
autoClose);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket() throws IOException {
|
|
||||||
return sslContext.getSocketFactory().createSocket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String mHttpsCertPath;
|
|
||||||
|
|
||||||
public CustomX509TrustManager(String httpsCertPath) {
|
|
||||||
mHttpsCertPath = httpsCertPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
|
||||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
|
||||||
throws CertificateException {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies certs against public key of the local syncthing instance
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void checkServerTrusted(java.security.cert.X509Certificate[] certs,
|
|
||||||
String authType) throws CertificateException {
|
|
||||||
InputStream inStream = null;
|
|
||||||
try {
|
|
||||||
inStream = new FileInputStream(mHttpsCertPath);
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
||||||
X509Certificate ca = (X509Certificate)
|
|
||||||
cf.generateCertificate(inStream);
|
|
||||||
for (X509Certificate cert : certs) {
|
|
||||||
cert.verify(ca.getPublicKey());
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException |NoSuchAlgorithmException|InvalidKeyException|
|
|
||||||
NoSuchProviderException |SignatureException e) {
|
|
||||||
throw new CertificateException("Untrusted Certificate!", e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (inStream != null)
|
|
||||||
inStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package com.nutomic.syncthingandroid.util;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
|
||||||
import org.apache.http.conn.scheme.Scheme;
|
|
||||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
|
||||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
|
||||||
|
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.UnrecoverableKeyException;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Wrapper for HTTPS Clients allowing the Syncthing https.pem CA
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class Https {
|
|
||||||
|
|
||||||
private static final String TAG = "HTTPS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a HTTPClient that verifies a custom PEM certificate
|
|
||||||
*
|
|
||||||
* @param httpsCertPath refers to the filepath of a SSL/TLS PEM certificate.
|
|
||||||
*/
|
|
||||||
public static HttpClient createHttpsClient(String httpsCertPath) {
|
|
||||||
try {
|
|
||||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
|
||||||
ctx.init(null, new TrustManager[] { new CustomX509TrustManager(httpsCertPath) },
|
|
||||||
new SecureRandom());
|
|
||||||
HttpClient client = new DefaultHttpClient();
|
|
||||||
SSLSocketFactory ssf = new CustomX509TrustManager.CustomSSLSocketFactory(ctx);
|
|
||||||
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
|
||||||
ClientConnectionManager ccm = client.getConnectionManager();
|
|
||||||
SchemeRegistry sr = ccm.getSchemeRegistry();
|
|
||||||
sr.register(new Scheme("https", ssf, 443));
|
|
||||||
return new DefaultHttpClient(ccm,
|
|
||||||
client.getParams());
|
|
||||||
} catch (NoSuchAlgorithmException|KeyManagementException|KeyStoreException|
|
|
||||||
UnrecoverableKeyException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue