diff --git a/aware-core/build.gradle b/aware-core/build.gradle index f572208a8..afcb509a2 100644 --- a/aware-core/build.gradle +++ b/aware-core/build.gradle @@ -8,7 +8,7 @@ android { versionCode version_code versionName version_readable targetSdkVersion 25 - minSdkVersion 10 //Android 2.3.3 + minSdkVersion 11 //Android 3.0 } signingConfigs { diff --git a/aware-core/src/main/java/com/aware/utils/Aware_Plugin.java b/aware-core/src/main/java/com/aware/utils/Aware_Plugin.java index 531d3232a..3f0d2888b 100644 --- a/aware-core/src/main/java/com/aware/utils/Aware_Plugin.java +++ b/aware-core/src/main/java/com/aware/utils/Aware_Plugin.java @@ -169,7 +169,7 @@ public void onReceive(Context context, Intent intent) { CONTEXT_PRODUCER.onContext(); } } - if (intent.getAction().equals(Aware.ACTION_AWARE_SYNC_DATA) && Aware.getSetting(context, Aware_Preferences.STATUS_WEBSERVICE).equals("true")) { + if (intent.getAction().equals(Aware.ACTION_AWARE_SYNC_DATA)) { if (DATABASE_TABLES != null && TABLES_FIELDS != null && CONTEXT_URIS != null) { for (int i = 0; i < DATABASE_TABLES.length; i++) { Intent webserviceHelper = new Intent(context, WebserviceHelper.class); diff --git a/aware-core/src/main/java/com/aware/utils/Aware_Sensor.java b/aware-core/src/main/java/com/aware/utils/Aware_Sensor.java index 17e42a9a9..e5dd95613 100644 --- a/aware-core/src/main/java/com/aware/utils/Aware_Sensor.java +++ b/aware-core/src/main/java/com/aware/utils/Aware_Sensor.java @@ -154,7 +154,7 @@ public void onReceive(Context context, Intent intent) { CONTEXT_PRODUCER.onContext(); } } - if (intent.getAction().equals(Aware.ACTION_AWARE_SYNC_DATA) && Aware.getSetting(context, Aware_Preferences.STATUS_WEBSERVICE).equals("true")) { + if (intent.getAction().equals(Aware.ACTION_AWARE_SYNC_DATA)) { if (DATABASE_TABLES != null && TABLES_FIELDS != null && CONTEXT_URIS != null) { for (int i = 0; i < DATABASE_TABLES.length; i++) { Intent webserviceHelper = new Intent(context, WebserviceHelper.class); diff --git a/aware-core/src/main/java/com/aware/utils/SSLManager.java b/aware-core/src/main/java/com/aware/utils/SSLManager.java index da1eddfa5..0426238f7 100644 --- a/aware-core/src/main/java/com/aware/utils/SSLManager.java +++ b/aware-core/src/main/java/com/aware/utils/SSLManager.java @@ -24,9 +24,17 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Date; import java.util.concurrent.ExecutionException; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLPeerUnverifiedException; + /** * Created by denzil on 15/12/15. * This class will make sure we have the latest server SSL certificate to allow downloads from self-hosted servers @@ -37,6 +45,7 @@ public class SSLManager { * Handle a study URL. Fetch data from query parameters if it is there. Otherwise, * use the classic method of downloading the certificate over http. Enforces the key * management policy. + * * @param context app context * @param url full URL, including protocol and query arguments * @param block if true, this method blocks, otherwise downloading is in background. @@ -68,14 +77,57 @@ public static void handleUrl(Context context, String url, boolean block) { Log.d(Aware.TAG, "Certificates: Already present and key_management=once: " + hostname); } } else { - if (!hasCertificate(context, hostname)) { - if (Aware.DEBUG) Log.d(Aware.TAG, "Certificates: Downloading certificate: " + hostname); - downloadCertificate(context, hostname, block); + try { + URL javaURL = new URL(url); + if (!hasCertificate(context, hostname) && (getCertificateExpiration(javaURL) != null && System.currentTimeMillis() >= getCertificateExpiration(javaURL).getTime())) { + if (Aware.DEBUG) + Log.d(Aware.TAG, "Certificates: Downloading certificate: " + hostname); + + downloadCertificate(context, hostname, block); + } + } catch (MalformedURLException e) { + e.printStackTrace(); } } } } + /** + * Taken from https://www.experts-exchange.com/questions/27668989/Getting-SSL-Certificate-expiry-date.html + * + * @param url + * @return + */ + public static Date getCertificateExpiration(URL url) { + try { + URLConnection conn = url.openConnection(); + if (conn instanceof HttpsURLConnection) { + // retrieve the N-length signing chain for the server certificates + // certs[0] is the server's certificate + // certs[1] - certs[N-1] are the intermediate authorities that signed the cert + // certs[N] is the root certificate authority of the chain + Certificate[] certs = ((HttpsURLConnection) conn).getServerCertificates(); + if (certs.length > 0 && certs[0] instanceof X509Certificate) { + // certs[0] is an X.509 certificate, return its "notAfter" date + return ((X509Certificate) certs[0]).getNotAfter(); + } + } + // connection is not HTTPS or server is not signed with an X.509 certificate, return null + return null; + } catch (SSLPeerUnverifiedException spue) { + // connection to server is not verified, unable to get certificates + return null; + } catch (IllegalStateException ise) { + // shouldn't get here -- indicates attempt to get certificates before + // connection is established + return null; + } catch (IOException ioe) { + // error connecting to URL -- this must be caught last since + // other exceptions are subclasses of IOException + return null; + } + } + /** * Classic method: Download certificate unconditionally. This is the old * method of certificate fetching. This method blocks if the block parameter is true, @@ -263,7 +315,7 @@ private static void setCertificate(Context context, String hostname, String cert * Load HTTPS certificate from server: server.crt * * @param context context - * @param server server URL, http://{hostname}/index.php + * @param server server URL, http://{hostname}/index.php * @return FileInputStream of certificate * @throws FileNotFoundException */ @@ -275,7 +327,7 @@ public static InputStream getHTTPS(Context context, String server) throws FileNo File root_folder; if (!context.getResources().getBoolean(R.bool.standalone)) { - root_folder = new File(Environment.getExternalStoragePublicDirectory("AWARE")+ "/credentials"); // sdcard/AWARE/ (shareable, does not delete when uninstalling) + root_folder = new File(Environment.getExternalStoragePublicDirectory("AWARE") + "/credentials"); // sdcard/AWARE/ (shareable, does not delete when uninstalling) } else { root_folder = new File(ContextCompat.getExternalFilesDirs(context, null)[0] + "/AWARE/credentials"); // sdcard/Android//AWARE/ (not shareable, deletes when uninstalling package) } @@ -298,7 +350,7 @@ public static InputStream getHTTPS(Context context, String server) throws FileNo * NOTE: different from getHTTPS. Here, we have the MQTT server address/IP as input parameter. * * @param context context - * @param server server hostname + * @param server server hostname * @return Input stream of opened certificate. * @throws FileNotFoundException */ @@ -308,7 +360,7 @@ static InputStream getCertificate(Context context, String server) throws FileNot File root_folder; if (!context.getResources().getBoolean(R.bool.standalone)) { - root_folder = new File(Environment.getExternalStoragePublicDirectory("AWARE")+ "/credentials"); // sdcard/AWARE/ (shareable, does not delete when uninstalling) + root_folder = new File(Environment.getExternalStoragePublicDirectory("AWARE") + "/credentials"); // sdcard/AWARE/ (shareable, does not delete when uninstalling) } else { root_folder = new File(ContextCompat.getExternalFilesDirs(context, null)[0] + "/AWARE/credentials"); // sdcard/Android//AWARE/ (not shareable, deletes when uninstalling package) } diff --git a/aware-core/src/main/java/com/aware/utils/WebserviceHelper.java b/aware-core/src/main/java/com/aware/utils/WebserviceHelper.java index 6d5d491f7..a9d5cf8c4 100644 --- a/aware-core/src/main/java/com/aware/utils/WebserviceHelper.java +++ b/aware-core/src/main/java/com/aware/utils/WebserviceHelper.java @@ -38,10 +38,7 @@ import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Calendar; -import java.util.Collections; -import java.util.HashMap; import java.util.Hashtable; -import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -64,28 +61,29 @@ public class WebserviceHelper extends Service { private static int notificationID = 0; private static int total_rows_synced = 0; - private static SyncQueue mSyncFastQueue; - private static Looper mServiceLooperFastQueue; - static ExecutorService executorFastQueue; + private SyncQueue mSyncFastQueue; + private Looper mServiceLooperFastQueue; + private ExecutorService executorFastQueue; - private static SyncQueue mSyncSlowQueueA; - private static Looper mServiceLooperSlowQueueA; - static ExecutorService executorSlowQueueA; + private SyncQueue mSyncSlowQueueA; + private Looper mServiceLooperSlowQueueA; + private ExecutorService executorSlowQueueA; - private static SyncQueue mSyncSlowQueueB; - private static Looper mServiceLooperSlowQueueB; - static ExecutorService executorSlowQueueB; + private SyncQueue mSyncSlowQueueB; + private Looper mServiceLooperSlowQueueB; + private ExecutorService executorSlowQueueB; private static boolean nextSlowQueue = false; - private static ArrayList highFrequencySensors = new ArrayList<>(); + private static final ArrayList highFrequencySensors = new ArrayList<>(); private static final ArrayList dontClearSensors = new ArrayList<>(); // Handler that receives messages from the thread private final class SyncQueue extends Handler { ExecutorService executor; - public int currentMessage; - public SyncQueue(Looper looper, ExecutorService executor) { + int currentMessage; + + SyncQueue(Looper looper, ExecutorService executor) { super(looper); // One thread to sync data with the server this.executor = executor; @@ -132,16 +130,13 @@ public void onCreate() { notificationID = 0; - HandlerThread threadFast = new HandlerThread("SyncFastQueue", - android.os.Process.THREAD_PRIORITY_BACKGROUND); + HandlerThread threadFast = new HandlerThread("SyncFastQueue", android.os.Process.THREAD_PRIORITY_BACKGROUND); threadFast.start(); - HandlerThread threadSlowA = new HandlerThread("SyncSlowAQueue", - android.os.Process.THREAD_PRIORITY_BACKGROUND); + HandlerThread threadSlowA = new HandlerThread("SyncSlowAQueue", android.os.Process.THREAD_PRIORITY_BACKGROUND); threadSlowA.start(); - HandlerThread threadSlowB = new HandlerThread("SyncSlowBQueue", - android.os.Process.THREAD_PRIORITY_BACKGROUND); + HandlerThread threadSlowB = new HandlerThread("SyncSlowBQueue", android.os.Process.THREAD_PRIORITY_BACKGROUND); threadSlowB.start(); // Get the HandlerThread's Looper and use it for our Handler @@ -196,7 +191,6 @@ private void notifyUser(Context mContext, String message, boolean dismiss, boole private int getBatchSize() { double availableRam; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - String load; try (RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r")) { load = reader.readLine(); @@ -258,7 +252,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { boolean DEBUG = Aware.getSetting(getApplicationContext(), Aware_Preferences.DEBUG_FLAG).equals("true"); if (intent.getAction().equals(ACTION_AWARE_WEBSERVICE_SYNC_TABLE)) { - if (Aware.getSetting(getApplicationContext(), Aware_Preferences.WEBSERVICE_CHARGING).equals("true")) { Intent batt = getApplicationContext().registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); int plugged = batt.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); @@ -273,24 +266,23 @@ public int onStartCommand(Intent intent, int flags, int startId) { } //Check if we are supposed to sync over WiFi only - if (Aware.getSetting(getApplicationContext(), Aware_Preferences.WEBSERVICE_WIFI_ONLY).equals("true")) { - ConnectivityManager connManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = connManager.getActiveNetworkInfo(); - if (!(activeNetwork != null && activeNetwork.getType() == ConnectivityManager.TYPE_WIFI && activeNetwork.isConnected())) { - if (Aware.DEBUG) - Log.d(Aware.TAG, "Sync data only over Wi-Fi. Will try again later..."); + if (!isWifiNeededAndConnected()) { + if (Aware.DEBUG) + Log.d(Aware.TAG, "Sync data only over Wi-Fi. Will try again later..."); - stopSelf(); - return START_REDELIVER_INTENT; - } + stopSelf(); + return START_REDELIVER_INTENT; } // For each start request, send a message to start a job and deliver the // start ID so we know which request we're stopping when we finish the job String table = intent.getStringExtra(EXTRA_TABLE); + + if (Aware.DEBUG) Log.d(Aware.TAG, "Processing " + table); + int tableHash = table.hashCode(); - if(mSyncFastQueue.currentMessage != tableHash && mSyncSlowQueueA.currentMessage != tableHash && mSyncSlowQueueB.currentMessage != tableHash && - !mSyncFastQueue.hasMessages(tableHash) && !mSyncSlowQueueA.hasMessages(tableHash) && !mSyncSlowQueueB.hasMessages(tableHash)){ + if (mSyncFastQueue.currentMessage != tableHash && mSyncSlowQueueA.currentMessage != tableHash && mSyncSlowQueueB.currentMessage != tableHash + && !mSyncFastQueue.hasMessages(tableHash) && !mSyncSlowQueueA.hasMessages(tableHash) && !mSyncSlowQueueB.hasMessages(tableHash)) { if (!highFrequencySensors.contains(table)) { //Non High Frequency sensors go together Message msg = buildMessage(mSyncFastQueue, intent, DEBUG, DEVICE_ID, WEBSERVER, WEBSERVICE_SIMPLE, WEBSERVICE_REMOVE_DATA, MAX_POST_SIZE, startId, notificationID++); mSyncFastQueue.sendMessage(msg); @@ -310,6 +302,15 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_REDELIVER_INTENT; } + public boolean isWifiNeededAndConnected() { + if (Aware.getSetting(getApplicationContext(), Aware_Preferences.WEBSERVICE_WIFI_ONLY).equals("true")) { + ConnectivityManager connManager = (ConnectivityManager) getApplicationContext().getSystemService(CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = connManager.getActiveNetworkInfo(); + return (activeNetwork != null && activeNetwork.getType() == ConnectivityManager.TYPE_WIFI && activeNetwork.isConnected()); + } + return true; + } + private Message buildMessage(SyncQueue queue, Intent intent, boolean DEBUG, String DEVICE_ID, String WEBSERVER, boolean WEBSERVICE_SIMPLE, boolean WEBSERVICE_REMOVE_DATA, int MAX_POST_SIZE, int startId, int notificationID) { Message msg = queue.obtainMessage(intent.getStringExtra(EXTRA_TABLE).hashCode()); Bundle bundle = new Bundle(); @@ -329,7 +330,6 @@ private Message buildMessage(SyncQueue queue, Intent intent, boolean DEBUG, Stri msg.setData(bundle); msg.arg1 = startId; return msg; - } /** @@ -570,13 +570,13 @@ private void performDatabaseSpaceMaintenance(Uri CONTENT_URI, long last, String[ cal.add(Calendar.MONTH, -1); if (Aware.DEBUG) Log.d(Aware.TAG, " Cleaning locally any data older than last month (yyyy/mm/dd): " + cal.get(Calendar.YEAR) + '/' + (cal.get(Calendar.MONTH) + 1) + '/' + cal.get(Calendar.DAY_OF_MONTH)); - rowsDeleted = mContext.getContentResolver().delete(CONTENT_URI, "timestamp < " + cal.getTimeInMillis()+ deleteSessionBasedSensors, null); + rowsDeleted = mContext.getContentResolver().delete(CONTENT_URI, "timestamp < " + cal.getTimeInMillis() + deleteSessionBasedSensors, null); break; case 3: //Daily cal.add(Calendar.DAY_OF_YEAR, -1); if (Aware.DEBUG) Log.d(Aware.TAG, "Cleaning locally any data older than today (yyyy/mm/dd): " + cal.get(Calendar.YEAR) + '/' + (cal.get(Calendar.MONTH) + 1) + '/' + cal.get(Calendar.DAY_OF_MONTH) + " from " + CONTENT_URI.toString()); - rowsDeleted = mContext.getContentResolver().delete(CONTENT_URI, "timestamp < " + cal.getTimeInMillis()+ deleteSessionBasedSensors, null); + rowsDeleted = mContext.getContentResolver().delete(CONTENT_URI, "timestamp < " + cal.getTimeInMillis() + deleteSessionBasedSensors, null); break; case 4: //Always (experimental) if (highFrequencySensors.contains(DATABASE_TABLE)) @@ -659,18 +659,9 @@ private long syncBatch(Cursor context_data) throws JSONException { return lastSynced; } - boolean isWifiNeededAndConnected() { - if (Aware.getSetting(mContext, Aware_Preferences.WEBSERVICE_WIFI_ONLY).equals("true")) { - ConnectivityManager connManager = (ConnectivityManager) mContext.getSystemService(CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = connManager.getActiveNetworkInfo(); - return (activeNetwork != null && activeNetwork.getType() == ConnectivityManager.TYPE_WIFI && activeNetwork.isConnected()); - } - return true; - } - - private boolean isTableAllowedForMaintenance(String table_name){ + private boolean isTableAllowedForMaintenance(String table_name) { //we need to keep the schedulers and aware_studies tables and on those tables that contain - if(table_name.equalsIgnoreCase("aware_studies") || table_name.equalsIgnoreCase("scheduler")) + if (table_name.equalsIgnoreCase("aware_studies") || table_name.equalsIgnoreCase("scheduler")) return false; return true; } @@ -683,7 +674,6 @@ public String call() throws Exception { String response = createRemoteTable(); if (response != null || WEBSERVICE_SIMPLE) { - try { String[] columnsStr = getTableColumnsNames(CONTENT_URI); String latest = getLatestRecordInDatabase(); @@ -694,8 +684,10 @@ public String call() throws Exception { if (Aware.DEBUG) { Log.d(Aware.TAG, "Sync " + DATABASE_TABLE + " exists: " + (response != null && response.length() == 0)); if (!latest.equals("[]")) Log.d(Aware.TAG, "Latest: " + latest); - if (study_condition.length() > 0) Log.d(Aware.TAG, "Since: " + study_condition); - if (total_records > 0) Log.d(Aware.TAG, "Rows to sync: " + total_records); + if (study_condition.length() > 0) + Log.d(Aware.TAG, "Since: " + study_condition); + if (total_records > 0) + Log.d(Aware.TAG, "Rows to sync: " + total_records); } if (total_records > 0) { diff --git a/aware-phone/build.gradle b/aware-phone/build.gradle index b70f4c763..85c1fcb8b 100755 --- a/aware-phone/build.gradle +++ b/aware-phone/build.gradle @@ -10,7 +10,7 @@ android { versionCode version_code versionName version_readable targetSdkVersion 25 - minSdkVersion 10 //Android 2.3.3 + minSdkVersion 11 //Android 2.3.3 } signingConfigs { diff --git a/aware-tests/build.gradle b/aware-tests/build.gradle index 1c7377161..38ab28f76 100644 --- a/aware-tests/build.gradle +++ b/aware-tests/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "com.aware.tests" - minSdkVersion 10 + minSdkVersion 11 targetSdkVersion 25 versionCode 1 versionName "1.0"