Browse Source

Merge commit '1cf41b93ff12f7e5fe12875db96bfcf7a568b8a6'

drone 4 năm trước cách đây
mục cha
commit
f899c2c668
25 tập tin đã thay đổi với 344 bổ sung171 xóa
  1. 2 2
      build.gradle
  2. 1 1
      scripts/analysis/findbugs-results.txt
  3. 4 1
      src/androidTest/java/com/nextcloud/client/RetryTestRule.kt
  4. 29 10
      src/androidTest/java/com/owncloud/android/UploadIT.java
  5. 7 14
      src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt
  6. 7 9
      src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentIT.kt
  7. 43 0
      src/main/java/com/nextcloud/client/device/BatteryStatus.kt
  8. 2 2
      src/main/java/com/nextcloud/client/device/DeviceModule.kt
  9. 3 3
      src/main/java/com/nextcloud/client/device/PowerManagementService.kt
  10. 21 12
      src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt
  11. 1 0
      src/main/java/com/nextcloud/client/jobs/AccountRemovalWork.kt
  12. 1 0
      src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
  13. 1 0
      src/main/java/com/nextcloud/client/jobs/ContactsImportWork.kt
  14. 2 0
      src/main/java/com/nextcloud/client/jobs/NotificationWork.kt
  15. 40 0
      src/main/java/com/nextcloud/client/network/Connectivity.kt
  16. 20 5
      src/main/java/com/nextcloud/client/network/ConnectivityService.java
  17. 23 51
      src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java
  18. 13 11
      src/main/java/com/owncloud/android/files/services/FileUploader.java
  19. 15 15
      src/main/java/com/owncloud/android/operations/UploadFileOperation.java
  20. 2 4
      src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
  21. 2 4
      src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
  22. 2 4
      src/main/java/com/owncloud/android/utils/ReceiversHelper.java
  23. 51 14
      src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt
  24. 13 3
      src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt
  25. 39 6
      src/test/java/com/nextcloud/client/network/ConnectivityServiceTest.kt

+ 2 - 2
build.gradle

@@ -119,7 +119,8 @@ android {
             'IconMissingDensityFolder',
             'IconMissingDensityFolder',
             'IconDensities',
             'IconDensities',
             'GoogleAppIndexingWarning',
             'GoogleAppIndexingWarning',
-            'MissingDefaultResource'
+            'MissingDefaultResource',
+            'InvalidPeriodicWorkRequestInterval' // crashes due to a bug in lint itself
     }
     }
 
 
     dexOptions {
     dexOptions {
@@ -305,7 +306,6 @@ dependencies {
     implementation 'com.github.albfernandez:juniversalchardet:2.0.3' // need this version for Android <7
     implementation 'com.github.albfernandez:juniversalchardet:2.0.3' // need this version for Android <7
     compileOnly 'com.google.code.findbugs:annotations:2.0.1'
     compileOnly 'com.google.code.findbugs:annotations:2.0.1'
     implementation 'commons-io:commons-io:2.6'
     implementation 'commons-io:commons-io:2.6'
-    implementation 'com.github.tobiaskaminsky:android-job:v1.2.6.1' // 'com.github.evernote:android-job:v1.2.5'
     implementation 'com.jakewharton:butterknife:10.2.1'
     implementation 'com.jakewharton:butterknife:10.2.1'
     kapt 'com.jakewharton:butterknife-compiler:10.2.1'
     kapt 'com.jakewharton:butterknife-compiler:10.2.1'
     implementation 'org.greenrobot:eventbus:3.2.0'
     implementation 'org.greenrobot:eventbus:3.2.0'

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-374
+373

+ 4 - 1
src/androidTest/java/com/nextcloud/client/RetryTestRule.kt

@@ -32,12 +32,15 @@ import org.junit.runners.model.Statement
  */
  */
 class RetryTestRule(val retryCount: Int = 5) : TestRule {
 class RetryTestRule(val retryCount: Int = 5) : TestRule {
 
 
-    private val TAG = RetryTestRule::class.java.simpleName
+    companion object {
+        private val TAG = RetryTestRule::class.java.simpleName
+    }
 
 
     override fun apply(base: Statement, description: Description): Statement {
     override fun apply(base: Statement, description: Description): Statement {
         return statement(base, description)
         return statement(base, description)
     }
     }
 
 
+    @Suppress("TooGenericExceptionCaught") // and this exactly what we want here
     private fun statement(base: Statement, description: Description): Statement {
     private fun statement(base: Statement, description: Description): Statement {
         return object : Statement() {
         return object : Statement() {
 
 

+ 29 - 10
src/androidTest/java/com/owncloud/android/UploadIT.java

@@ -1,11 +1,33 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
 package com.owncloud.android;
 package com.owncloud.android;
 
 
 import android.content.ContentResolver;
 import android.content.ContentResolver;
 
 
-import com.evernote.android.job.JobRequest;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManagerImpl;
 import com.nextcloud.client.account.UserAccountManagerImpl;
+import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.db.OCUpload;
@@ -15,6 +37,7 @@ import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 
 
+import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runner.RunWith;
@@ -39,13 +62,8 @@ public class UploadIT extends AbstractIT {
         }
         }
 
 
         @Override
         @Override
-        public boolean isOnlineWithWifi() {
-            return true;
-        }
-
-        @Override
-        public JobRequest.NetworkType getActiveNetworkType() {
-            return JobRequest.NetworkType.ANY;
+        public Connectivity getConnectivity() {
+            return Connectivity.CONNECTED_WIFI;
         }
         }
     };
     };
 
 
@@ -60,9 +78,10 @@ public class UploadIT extends AbstractIT {
             return false;
             return false;
         }
         }
 
 
+        @NotNull
         @Override
         @Override
-        public boolean isBatteryCharging() {
-            return false;
+        public BatteryStatus getBattery() {
+            return new BatteryStatus(false, 0);
         }
         }
     };
     };
 
 

+ 7 - 14
src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt

@@ -5,6 +5,7 @@
  * @author Tobias Kaminsky
  * @author Tobias Kaminsky
  * Copyright (C) 2020 Tobias Kaminsky
  * Copyright (C) 2020 Tobias Kaminsky
  * Copyright (C) 2020 Nextcloud GmbH
  * Copyright (C) 2020 Nextcloud GmbH
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * it under the terms of the GNU Affero General Public License as published by
@@ -21,10 +22,11 @@
  */
  */
 package com.owncloud.android.files.services
 package com.owncloud.android.files.services
 
 
-import com.evernote.android.job.JobRequest
 import com.nextcloud.client.account.UserAccountManager
 import com.nextcloud.client.account.UserAccountManager
 import com.nextcloud.client.account.UserAccountManagerImpl
 import com.nextcloud.client.account.UserAccountManagerImpl
+import com.nextcloud.client.device.BatteryStatus
 import com.nextcloud.client.device.PowerManagementService
 import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.network.Connectivity
 import com.nextcloud.client.network.ConnectivityService
 import com.nextcloud.client.network.ConnectivityService
 import com.owncloud.android.AbstractIT
 import com.owncloud.android.AbstractIT
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.datamodel.OCFile
@@ -45,17 +47,8 @@ class FileUploaderIT : AbstractIT() {
     var uploadsStorageManager: UploadsStorageManager? = null
     var uploadsStorageManager: UploadsStorageManager? = null
 
 
     val connectivityServiceMock: ConnectivityService = object : ConnectivityService {
     val connectivityServiceMock: ConnectivityService = object : ConnectivityService {
-        override fun isInternetWalled(): Boolean {
-            return false
-        }
-
-        override fun isOnlineWithWifi(): Boolean {
-            return true
-        }
-
-        override fun getActiveNetworkType(): JobRequest.NetworkType {
-            return JobRequest.NetworkType.ANY
-        }
+        override fun isInternetWalled(): Boolean = false
+        override fun getConnectivity(): Connectivity = Connectivity.CONNECTED_WIFI
     }
     }
 
 
     private val powerManagementServiceMock: PowerManagementService = object : PowerManagementService {
     private val powerManagementServiceMock: PowerManagementService = object : PowerManagementService {
@@ -65,8 +58,8 @@ class FileUploaderIT : AbstractIT() {
         override val isPowerSavingExclusionAvailable: Boolean
         override val isPowerSavingExclusionAvailable: Boolean
             get() = false
             get() = false
 
 
-        override val isBatteryCharging: Boolean
-            get() = false
+        override val battery: BatteryStatus
+            get() = BatteryStatus()
     }
     }
 
 
     @Before
     @Before

+ 7 - 9
src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentIT.kt

@@ -5,6 +5,7 @@
  * @author Tobias Kaminsky
  * @author Tobias Kaminsky
  * Copyright (C) 2020 Tobias Kaminsky
  * Copyright (C) 2020 Tobias Kaminsky
  * Copyright (C) 2020 Nextcloud GmbH
  * Copyright (C) 2020 Nextcloud GmbH
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * it under the terms of the GNU Affero General Public License as published by
@@ -26,10 +27,11 @@ import androidx.test.core.app.ActivityScenario
 import androidx.test.espresso.intent.rule.IntentsTestRule
 import androidx.test.espresso.intent.rule.IntentsTestRule
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
 import androidx.test.rule.GrantPermissionRule
-import com.evernote.android.job.JobRequest
 import com.facebook.testing.screenshot.Screenshot
 import com.facebook.testing.screenshot.Screenshot
 import com.nextcloud.client.account.UserAccountManagerImpl
 import com.nextcloud.client.account.UserAccountManagerImpl
+import com.nextcloud.client.device.BatteryStatus
 import com.nextcloud.client.device.PowerManagementService
 import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.network.Connectivity
 import com.nextcloud.client.network.ConnectivityService
 import com.nextcloud.client.network.ConnectivityService
 import com.nextcloud.client.preferences.AppPreferences
 import com.nextcloud.client.preferences.AppPreferences
 import com.nextcloud.client.preferences.AppPreferencesImpl
 import com.nextcloud.client.preferences.AppPreferencesImpl
@@ -71,12 +73,8 @@ class OCFileListFragmentIT : AbstractIT() {
             return false
             return false
         }
         }
 
 
-        override fun isOnlineWithWifi(): Boolean {
-            return true
-        }
-
-        override fun getActiveNetworkType(): JobRequest.NetworkType {
-            return JobRequest.NetworkType.ANY
+        override fun getConnectivity(): Connectivity {
+            return Connectivity.CONNECTED_WIFI
         }
         }
     }
     }
 
 
@@ -87,8 +85,8 @@ class OCFileListFragmentIT : AbstractIT() {
         override val isPowerSavingExclusionAvailable: Boolean
         override val isPowerSavingExclusionAvailable: Boolean
             get() = false
             get() = false
 
 
-        override val isBatteryCharging: Boolean
-            get() = false
+        override val battery: BatteryStatus
+            get() = BatteryStatus()
     }
     }
 
 
     @Test
     @Test

+ 43 - 0
src/main/java/com/nextcloud/client/device/BatteryStatus.kt

@@ -0,0 +1,43 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.client.device
+
+/**
+ * This class exposes battery status information
+ * in platform-independent way.
+ *
+ * @param isCharging true if device battery is charging
+ * @param level Battery level, from 0 to 100%
+ *
+ * @see [android.os.BatteryManager]
+ */
+data class BatteryStatus(val isCharging: Boolean = false, val level: Int = 0) {
+
+    companion object {
+        const val BATTERY_FULL = 100
+    }
+
+    /**
+     * True if battery is fully loaded, false otherwise.
+     * Some dodgy devices can report battery charging
+     * status as "battery full".
+     */
+    val isFull: Boolean get() = level >= BATTERY_FULL
+}

+ 2 - 2
src/main/java/com/nextcloud/client/device/DeviceModule.kt

@@ -4,7 +4,7 @@
  * @author Chris Narkiewicz
  * @author Chris Narkiewicz
  * @author Tobias Kaminsky
  * @author Tobias Kaminsky
  *
  *
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  * Copyright (C) 2019 Tobias Kaminsky
  * Copyright (C) 2019 Tobias Kaminsky
  * Copyright (C) 2019 Nextcloud GmbH
  * Copyright (C) 2019 Nextcloud GmbH
  *
  *
@@ -38,7 +38,7 @@ class DeviceModule {
         val platformPowerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
         val platformPowerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
         return PowerManagementServiceImpl(
         return PowerManagementServiceImpl(
             context = context,
             context = context,
-            powerManager = platformPowerManager,
+            platformPowerManager = platformPowerManager,
             deviceInfo = DeviceInfo(),
             deviceInfo = DeviceInfo(),
             preferences = preferences
             preferences = preferences
         )
         )

+ 3 - 3
src/main/java/com/nextcloud/client/device/PowerManagementService.kt

@@ -3,7 +3,7 @@
  *
  *
  * @author Chris Narkiewicz
  * @author Chris Narkiewicz
  *
  *
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * it under the terms of the GNU General Public License as published by
@@ -45,7 +45,7 @@ interface PowerManagementService {
     val isPowerSavingExclusionAvailable: Boolean
     val isPowerSavingExclusionAvailable: Boolean
 
 
     /**
     /**
-     * Checks if battery is charging using any hardware supported means.
+     * Checks current battery status using platform [android.os.BatteryManager]
      */
      */
-    val isBatteryCharging: Boolean
+    val battery: BatteryStatus
 }
 }

+ 21 - 12
src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt

@@ -3,7 +3,7 @@
  *
  *
  * @author Chris Narkiewicz
  * @author Chris Narkiewicz
  *
  *
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * it under the terms of the GNU General Public License as published by
@@ -33,7 +33,7 @@ import com.nextcloud.client.preferences.AppPreferencesImpl
 
 
 internal class PowerManagementServiceImpl(
 internal class PowerManagementServiceImpl(
     private val context: Context,
     private val context: Context,
-    private val powerManager: PowerManager,
+    private val platformPowerManager: PowerManager,
     private val preferences: AppPreferences,
     private val preferences: AppPreferences,
     private val deviceInfo: DeviceInfo = DeviceInfo()
     private val deviceInfo: DeviceInfo = DeviceInfo()
 ) : PowerManagementService {
 ) : PowerManagementService {
@@ -62,7 +62,7 @@ internal class PowerManagementServiceImpl(
 
 
             @TargetApi(Build.VERSION_CODES.LOLLIPOP)
             @TargetApi(Build.VERSION_CODES.LOLLIPOP)
             if (deviceInfo.apiLevel >= Build.VERSION_CODES.LOLLIPOP) {
             if (deviceInfo.apiLevel >= Build.VERSION_CODES.LOLLIPOP) {
-                return powerManager.isPowerSaveMode
+                return platformPowerManager.isPowerSaveMode
             }
             }
             // For older versions, we just say that device is not in power save mode
             // For older versions, we just say that device is not in power save mode
             return false
             return false
@@ -71,16 +71,25 @@ internal class PowerManagementServiceImpl(
     override val isPowerSavingExclusionAvailable: Boolean
     override val isPowerSavingExclusionAvailable: Boolean
         get() = deviceInfo.vendor in OVERLY_AGGRESSIVE_POWER_SAVING_VENDORS
         get() = deviceInfo.vendor in OVERLY_AGGRESSIVE_POWER_SAVING_VENDORS
 
 
-    override val isBatteryCharging: Boolean
+    @Suppress("MagicNumber") // 100% is 100, we're not doing Cobol
+    override val battery: BatteryStatus
         get() {
         get() {
             val intent: Intent? = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
             val intent: Intent? = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
-            val plugged = intent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) ?: 0
-            return when {
-                plugged == BatteryManager.BATTERY_PLUGGED_USB -> true
-                plugged == BatteryManager.BATTERY_PLUGGED_AC -> true
-                deviceInfo.apiLevel >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
-                    plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS -> true
-                else -> false
-            }
+            val isCharging = intent?.let {
+                val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
+                when {
+                    plugged == BatteryManager.BATTERY_PLUGGED_USB -> true
+                    plugged == BatteryManager.BATTERY_PLUGGED_AC -> true
+                    deviceInfo.apiLevel >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
+                        plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS -> true
+                    else -> false
+                }
+            } ?: false
+            val level = intent?.let { it ->
+                val level: Int = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
+                val scale: Int = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
+                (level * 100 / scale.toFloat()).toInt()
+            } ?: 0
+            return BatteryStatus(isCharging, level)
         }
         }
 }
 }

+ 1 - 0
src/main/java/com/nextcloud/client/jobs/AccountRemovalWork.kt

@@ -61,6 +61,7 @@ import java.util.ArrayList
 /**
 /**
  * Removes account and all local files
  * Removes account and all local files
  */
  */
+@Suppress("LongParameterList") // legacy code
 class AccountRemovalWork(
 class AccountRemovalWork(
     private val context: Context,
     private val context: Context,
     params: WorkerParameters,
     params: WorkerParameters,

+ 1 - 0
src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt

@@ -63,6 +63,7 @@ class BackgroundJobFactory @Inject constructor(
     private val eventBus: EventBus
     private val eventBus: EventBus
 ) : WorkerFactory() {
 ) : WorkerFactory() {
 
 
+    @Suppress("ComplexMethod") // it's just a trivial dispatch
     override fun createWorker(
     override fun createWorker(
         context: Context,
         context: Context,
         workerClassName: String,
         workerClassName: String,

+ 1 - 0
src/main/java/com/nextcloud/client/jobs/ContactsImportWork.kt

@@ -55,6 +55,7 @@ class ContactsImportWork(
         const val SELECTED_CONTACTS_INDICES = "selected_contacts_indices"
         const val SELECTED_CONTACTS_INDICES = "selected_contacts_indices"
     }
     }
 
 
+    @Suppress("ComplexMethod", "NestedBlockDepth") // legacy code
     override fun doWork(): Result {
     override fun doWork(): Result {
         val vCardFilePath = inputData.getString(VCARD_FILE_PATH) ?: ""
         val vCardFilePath = inputData.getString(VCARD_FILE_PATH) ?: ""
         val contactsAccountName = inputData.getString(ACCOUNT_NAME)
         val contactsAccountName = inputData.getString(ACCOUNT_NAME)

+ 2 - 0
src/main/java/com/nextcloud/client/jobs/NotificationWork.kt

@@ -129,6 +129,7 @@ class NotificationWork constructor(
         return Result.success()
         return Result.success()
     }
     }
 
 
+    @Suppress("LongMethod") // legacy code
     private fun sendNotification(notification: Notification, user: User) {
     private fun sendNotification(notification: Notification, user: User) {
         val randomId = SecureRandom()
         val randomId = SecureRandom()
         val file = notification.subjectRichParameters["file"]
         val file = notification.subjectRichParameters["file"]
@@ -237,6 +238,7 @@ class NotificationWork constructor(
             this.accountManager = accountManager
             this.accountManager = accountManager
         }
         }
 
 
+        @Suppress("ComplexMethod") // legacy code
         override fun onReceive(context: Context, intent: Intent) {
         override fun onReceive(context: Context, intent: Intent) {
             AndroidInjection.inject(this, context)
             AndroidInjection.inject(this, context)
             val numericNotificationId = intent.getIntExtra(NUMERIC_NOTIFICATION_ID, 0)
             val numericNotificationId = intent.getIntExtra(NUMERIC_NOTIFICATION_ID, 0)

+ 40 - 0
src/main/java/com/nextcloud/client/network/Connectivity.kt

@@ -0,0 +1,40 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.client.network
+
+data class Connectivity(
+    val isConnected: Boolean = false,
+    val isMetered: Boolean = false,
+    val isWifi: Boolean = false,
+    val isServerAvailable: Boolean? = null
+) {
+    companion object {
+        @JvmField
+        val DISCONNECTED = Connectivity()
+
+        @JvmField
+        val CONNECTED_WIFI = Connectivity(
+            isConnected = true,
+            isMetered = false,
+            isWifi = true,
+            isServerAvailable = true
+        )
+    }
+}

+ 20 - 5
src/main/java/com/nextcloud/client/network/ConnectivityService.java

@@ -2,7 +2,7 @@
  * Nextcloud Android client application
  * Nextcloud Android client application
  *
  *
  * @author Chris Narkiewicz
  * @author Chris Narkiewicz
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * it under the terms of the GNU Affero General Public License as published by
@@ -20,10 +20,25 @@
 
 
 package com.nextcloud.client.network;
 package com.nextcloud.client.network;
 
 
-import com.evernote.android.job.JobRequest;
-
+/**
+ * This service provides information about current network connectivity
+ * and server reachableity.
+ */
 public interface ConnectivityService {
 public interface ConnectivityService {
+
+    /**
+     * Check if server is accessible by issuing HTTP status check request.
+     * Since this call involves network traffic, it should not be called
+     * on a main thread.
+     *
+     * @return True if server is unreachable, false otherwise
+     */
     boolean isInternetWalled();
     boolean isInternetWalled();
-    boolean isOnlineWithWifi();
-    JobRequest.NetworkType getActiveNetworkType();
+
+    /**
+     * Get current network connectivity status.
+     *
+     * @return Network connectivity status in platform-agnostic format
+     */
+    Connectivity getConnectivity();
 }
 }

+ 23 - 51
src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java

@@ -23,7 +23,6 @@ package com.nextcloud.client.network;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo;
 
 
-import com.evernote.android.job.JobRequest;
 import com.nextcloud.client.account.Server;
 import com.nextcloud.client.account.Server;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.logger.Logger;
 import com.nextcloud.client.logger.Logger;
@@ -43,7 +42,7 @@ class ConnectivityServiceImpl implements ConnectivityService {
 
 
     private final static String TAG = ConnectivityServiceImpl.class.getName();
     private final static String TAG = ConnectivityServiceImpl.class.getName();
 
 
-    private final ConnectivityManager connectivityManager;
+    private final ConnectivityManager platformConnectivityManager;
     private final UserAccountManager accountManager;
     private final UserAccountManager accountManager;
     private final ClientFactory clientFactory;
     private final ClientFactory clientFactory;
     private final GetRequestBuilder requestBuilder;
     private final GetRequestBuilder requestBuilder;
@@ -56,12 +55,12 @@ class ConnectivityServiceImpl implements ConnectivityService {
         }
         }
     }
     }
 
 
-    ConnectivityServiceImpl(ConnectivityManager connectivityManager,
+    ConnectivityServiceImpl(ConnectivityManager platformConnectivityManager,
                             UserAccountManager accountManager,
                             UserAccountManager accountManager,
                             ClientFactory clientFactory,
                             ClientFactory clientFactory,
                             GetRequestBuilder requestBuilder,
                             GetRequestBuilder requestBuilder,
                             Logger logger) {
                             Logger logger) {
-        this.connectivityManager = connectivityManager;
+        this.platformConnectivityManager = platformConnectivityManager;
         this.accountManager = accountManager;
         this.accountManager = accountManager;
         this.clientFactory = clientFactory;
         this.clientFactory = clientFactory;
         this.requestBuilder = requestBuilder;
         this.requestBuilder = requestBuilder;
@@ -70,7 +69,8 @@ class ConnectivityServiceImpl implements ConnectivityService {
 
 
     @Override
     @Override
     public boolean isInternetWalled() {
     public boolean isInternetWalled() {
-        if (isOnlineWithWifi()) {
+        Connectivity c = getConnectivity();
+        if (c.isConnected() && c.isWifi()) {
 
 
             GetMethod get = null;
             GetMethod get = null;
             try {
             try {
@@ -116,65 +116,37 @@ class ConnectivityServiceImpl implements ConnectivityService {
                 }
                 }
             }
             }
         } else {
         } else {
-            return getActiveNetworkType() == JobRequest.NetworkType.ANY;
+            return !getConnectivity().isConnected();
         }
         }
 
 
         return true;
         return true;
     }
     }
 
 
     @Override
     @Override
-    public boolean isOnlineWithWifi() {
-        try {
-            NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
-
-            if (activeNetwork.isConnectedOrConnecting()) {
-                switch (activeNetwork.getType()) {
-                    case ConnectivityManager.TYPE_VPN:
-                        // check if any other network is wifi
-                        for (NetworkInfo networkInfo : connectivityManager.getAllNetworkInfo()) {
-                            if (networkInfo.isConnectedOrConnecting() &&
-                                networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
-                                return true;
-                            }
-                        }
-                        return false;
-
-                    case ConnectivityManager.TYPE_WIFI:
-                        return true;
-
-                    default:
-                        return false;
-                }
-            } else {
-                return false;
-            }
-        } catch (NullPointerException exception) {
-            return false;
-        }
-    }
-
-    @Override
-    public JobRequest.NetworkType getActiveNetworkType() {
+    public Connectivity getConnectivity() {
         NetworkInfo networkInfo;
         NetworkInfo networkInfo;
         try {
         try {
-            networkInfo = connectivityManager.getActiveNetworkInfo();
+            networkInfo = platformConnectivityManager.getActiveNetworkInfo();
         } catch (Throwable t) {
         } catch (Throwable t) {
-            return JobRequest.NetworkType.ANY;
-        }
-
-        if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {
-            return JobRequest.NetworkType.ANY;
+            networkInfo = null; // no network available or no information (permission denied?)
         }
         }
 
 
-        boolean metered = ConnectivityManagerCompat.isActiveNetworkMetered(connectivityManager);
-        if (!metered) {
-            return JobRequest.NetworkType.UNMETERED;
+        if (networkInfo != null) {
+            boolean isConnected = networkInfo.isConnectedOrConnecting();
+            boolean isMetered = ConnectivityManagerCompat.isActiveNetworkMetered(platformConnectivityManager);
+            boolean isWifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI || isAnyOtherNetworkWifi();
+            return new Connectivity(isConnected, isMetered, isWifi, null);
+        } else {
+            return Connectivity.DISCONNECTED;
         }
         }
+    }
 
 
-        if (networkInfo.isRoaming()) {
-            return JobRequest.NetworkType.CONNECTED;
-        } else {
-            return JobRequest.NetworkType.NOT_ROAMING;
+    private boolean isAnyOtherNetworkWifi() {
+        for (NetworkInfo networkInfo : platformConnectivityManager.getAllNetworkInfo()) {
+            if (networkInfo.isConnectedOrConnecting() && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+                return true;
+            }
         }
         }
+        return false;
     }
     }
 }
 }

+ 13 - 11
src/main/java/com/owncloud/android/files/services/FileUploader.java

@@ -9,7 +9,7 @@
  *
  *
  *  Copyright (C) 2012 Bartek Przybylski
  *  Copyright (C) 2012 Bartek Przybylski
  *  Copyright (C) 2012-2016 ownCloud Inc.
  *  Copyright (C) 2012-2016 ownCloud Inc.
- *  Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ *  Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  *  This program is free software: you can redistribute it and/or modify
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2,
  *  it under the terms of the GNU General Public License version 2,
@@ -47,10 +47,10 @@ import android.os.Parcelable;
 import android.os.Process;
 import android.os.Process;
 import android.util.Pair;
 import android.util.Pair;
 
 
-import com.evernote.android.job.JobRequest;
-import com.evernote.android.job.util.Device;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
@@ -1007,11 +1007,12 @@ public class FileUploader extends Service
         boolean resultMatch;
         boolean resultMatch;
         boolean accountMatch;
         boolean accountMatch;
 
 
-        boolean gotNetwork = connectivityService.getActiveNetworkType() != JobRequest.NetworkType.ANY &&
-            !connectivityService.isInternetWalled();
-        boolean gotWifi = gotNetwork && Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED);
-        boolean charging = Device.getBatteryStatus(context).isCharging();
-        boolean isPowerSaving = powerManagementService.isPowerSavingEnabled();
+        final Connectivity connectivity = connectivityService.getConnectivity();
+        final boolean gotNetwork = connectivity.isConnected() && !connectivityService.isInternetWalled();
+        final boolean gotWifi = connectivity.isWifi();
+        final BatteryStatus batteryStatus = powerManagementService.getBattery();
+        final boolean charging = batteryStatus.isCharging() || batteryStatus.isFull();
+        final boolean isPowerSaving = powerManagementService.isPowerSavingEnabled();
 
 
         for (OCUpload failedUpload : failedUploads) {
         for (OCUpload failedUpload : failedUploads) {
             accountMatch = account == null || account.name.equals(failedUpload.getAccountName());
             accountMatch = account == null || account.name.equals(failedUpload.getAccountName());
@@ -1027,7 +1028,7 @@ public class FileUploader extends Service
                         uploadsStorageManager.updateUpload(failedUpload);
                         uploadsStorageManager.updateUpload(failedUpload);
                     }
                     }
                 } else {
                 } else {
-                    charging = charging || Device.getBatteryStatus(context).getBatteryPercent() == 1;
+
                     if (!isPowerSaving && gotNetwork && canUploadBeRetried(failedUpload, gotWifi, charging)) {
                     if (!isPowerSaving && gotNetwork && canUploadBeRetried(failedUpload, gotWifi, charging)) {
                         retryUpload(context, currentAccount, failedUpload);
                         retryUpload(context, currentAccount, failedUpload);
                     }
                     }
@@ -1286,9 +1287,10 @@ public class FileUploader extends Service
             Context context = MainApp.getAppContext();
             Context context = MainApp.getAppContext();
             if (context != null) {
             if (context != null) {
                 ResultCode cancelReason = null;
                 ResultCode cancelReason = null;
-                if (mCurrentUpload.isWifiRequired() && !Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
+                Connectivity connectivity = connectivityService.getConnectivity();
+                if (mCurrentUpload.isWifiRequired() && connectivity.isWifi()) {
                     cancelReason = ResultCode.DELAYED_FOR_WIFI;
                     cancelReason = ResultCode.DELAYED_FOR_WIFI;
-                } else if (mCurrentUpload.isChargingRequired() && !Device.getBatteryStatus(context).isCharging()) {
+                } else if (mCurrentUpload.isChargingRequired() && !powerManagementService.getBattery().isCharging()) {
                     cancelReason = ResultCode.DELAYED_FOR_CHARGING;
                     cancelReason = ResultCode.DELAYED_FOR_CHARGING;
                 } else if (!mCurrentUpload.isIgnoringPowerSaveMode() && powerManagementService.isPowerSavingEnabled()) {
                 } else if (!mCurrentUpload.isIgnoringPowerSaveMode() && powerManagementService.isPowerSavingEnabled()) {
                     cancelReason = ResultCode.DELAYED_IN_POWER_SAVE_MODE;
                     cancelReason = ResultCode.DELAYED_IN_POWER_SAVE_MODE;

+ 15 - 15
src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -4,7 +4,7 @@
  * @author David A. Velasco
  * @author David A. Velasco
  * @author Chris Narkiewicz
  * @author Chris Narkiewicz
  * Copyright (C) 2016 ownCloud GmbH.
  * Copyright (C) 2016 ownCloud GmbH.
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
  * it under the terms of the GNU General Public License version 2,
@@ -29,10 +29,10 @@ import android.os.Build;
 import android.text.TextUtils;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Log;
 
 
-import com.evernote.android.job.JobRequest;
-import com.evernote.android.job.util.Device;
 import com.google.gson.reflect.TypeToken;
 import com.google.gson.reflect.TypeToken;
+import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.DecryptedFolderMetadata;
 import com.owncloud.android.datamodel.DecryptedFolderMetadata;
@@ -144,11 +144,11 @@ public class UploadFileOperation extends SyncOperation {
 
 
     private RequestEntity mEntity;
     private RequestEntity mEntity;
 
 
-    final private Account mAccount;
-    final private OCUpload mUpload;
-    final private UploadsStorageManager uploadsStorageManager;
-    final private ConnectivityService connectivityService;
-    final private PowerManagementService powerManagementService;
+    private final Account mAccount;
+    private final OCUpload mUpload;
+    private final UploadsStorageManager uploadsStorageManager;
+    private final ConnectivityService connectivityService;
+    private final PowerManagementService powerManagementService;
 
 
     private boolean encryptedAncestor;
     private boolean encryptedAncestor;
 
 
@@ -594,7 +594,7 @@ public class UploadFileOperation extends SyncOperation {
 
 
             /// perform the upload
             /// perform the upload
             if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
             if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
-                boolean onWifiConnection = connectivityService.isOnlineWithWifi();
+                boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
 
 
                 mUploadOperation = new ChunkedFileUploadRemoteOperation(encryptedTempFile.getAbsolutePath(),
                 mUploadOperation = new ChunkedFileUploadRemoteOperation(encryptedTempFile.getAbsolutePath(),
                                                                         mFile.getParentRemotePath() + encryptedFileName,
                                                                         mFile.getParentRemotePath() + encryptedFileName,
@@ -718,20 +718,20 @@ public class UploadFileOperation extends SyncOperation {
         RemoteOperationResult remoteOperationResult = null;
         RemoteOperationResult remoteOperationResult = null;
 
 
         // check that internet is not behind walled garden
         // check that internet is not behind walled garden
-        if (Device.getNetworkType(mContext).equals(JobRequest.NetworkType.ANY) ||
-                connectivityService.isInternetWalled()) {
+        if (!connectivityService.getConnectivity().isConnected() || connectivityService.isInternetWalled()) {
             remoteOperationResult =  new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION);
             remoteOperationResult =  new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION);
         }
         }
 
 
         // check that connectivity conditions are met and delays the upload otherwise
         // check that connectivity conditions are met and delays the upload otherwise
-        if (mOnWifiOnly && !Device.getNetworkType(mContext).equals(JobRequest.NetworkType.UNMETERED)) {
+        Connectivity connectivity = connectivityService.getConnectivity();
+        if (mOnWifiOnly && connectivity.isWifi()) {
             Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
             Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
             remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
             remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
         }
         }
 
 
         // check if charging conditions are met and delays the upload otherwise
         // check if charging conditions are met and delays the upload otherwise
-        if (mWhileChargingOnly && !Device.getBatteryStatus(mContext).isCharging()
-                && Device.getBatteryStatus(mContext).getBatteryPercent() < 1) {
+        final BatteryStatus battery = powerManagementService.getBattery();
+        if (mWhileChargingOnly && battery.isCharging()) {
             Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath());
             Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath());
             remoteOperationResult =  new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
             remoteOperationResult =  new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
         }
         }
@@ -827,7 +827,7 @@ public class UploadFileOperation extends SyncOperation {
 
 
             // perform the upload
             // perform the upload
             if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
             if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
-                boolean onWifiConnection = connectivityService.isOnlineWithWifi();
+                boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
 
 
                 mUploadOperation = new ChunkedFileUploadRemoteOperation(mFile.getStoragePath(),
                 mUploadOperation = new ChunkedFileUploadRemoteOperation(mFile.getStoragePath(),
                                                                         mFile.getRemotePath(), mFile.getMimeType(),
                                                                         mFile.getRemotePath(), mFile.getMimeType(),

+ 2 - 4
src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -9,7 +9,7 @@
  *
  *
  * Copyright (C) 2015 ownCloud Inc.
  * Copyright (C) 2015 ownCloud Inc.
  * Copyright (C) 2018 Andy Scherzinger
  * Copyright (C) 2018 Andy Scherzinger
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
  * it under the terms of the GNU General Public License version 2,
@@ -45,7 +45,6 @@ import android.util.Log;
 import android.view.View;
 import android.view.View;
 import android.webkit.MimeTypeMap;
 import android.webkit.MimeTypeMap;
 
 
-import com.evernote.android.job.JobRequest;
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.network.ConnectivityService;
@@ -219,8 +218,7 @@ public class FileOperationsHelper {
             }
             }
 
 
             // if offline or walled garden, show old version with warning
             // if offline or walled garden, show old version with warning
-            if (connectivityService.getActiveNetworkType() == JobRequest.NetworkType.ANY ||
-                    connectivityService.isInternetWalled()) {
+            if (!connectivityService.getConnectivity().isConnected() || connectivityService.isInternetWalled()) {
                 DisplayUtils.showSnackMessage(fileActivity, R.string.file_not_synced);
                 DisplayUtils.showSnackMessage(fileActivity, R.string.file_not_synced);
                 EventBus.getDefault().post(new SyncEventFinished(intent));
                 EventBus.getDefault().post(new SyncEventFinished(intent));
 
 

+ 2 - 4
src/main/java/com/owncloud/android/utils/FilesSyncHelper.java

@@ -6,7 +6,7 @@
  *
  *
  * Copyright (C) 2017 Mario Danic
  * Copyright (C) 2017 Mario Danic
  * Copyright (C) 2017 Nextcloud
  * Copyright (C) 2017 Nextcloud
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software; you can redistribute it and/or
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -31,7 +31,6 @@ import android.net.Uri;
 import android.os.Build;
 import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.MediaStore;
 
 
-import com.evernote.android.job.JobRequest;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.device.PowerManagementService;
@@ -205,8 +204,7 @@ public final class FilesSyncHelper {
         }
         }
 
 
         new Thread(() -> {
         new Thread(() -> {
-            if (connectivityService.getActiveNetworkType() != JobRequest.NetworkType.ANY &&
-                    !connectivityService.isInternetWalled()) {
+            if (connectivityService.getConnectivity().isConnected() && !connectivityService.isInternetWalled()) {
                 FileUploader.retryFailedUploads(
                 FileUploader.retryFailedUploads(
                     context,
                     context,
                     null,
                     null,

+ 2 - 4
src/main/java/com/owncloud/android/utils/ReceiversHelper.java

@@ -5,7 +5,7 @@
  * @author Chris Narkiewicz
  * @author Chris Narkiewicz
  * Copyright (C) 2017 Mario Danic
  * Copyright (C) 2017 Mario Danic
  * Copyright (C) 2017 Nextcloud
  * Copyright (C) 2017 Nextcloud
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software; you can redistribute it and/or
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -27,8 +27,6 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentFilter;
 
 
-import com.evernote.android.job.JobRequest;
-import com.evernote.android.job.util.Device;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.network.ConnectivityService;
@@ -57,7 +55,7 @@ public final class ReceiversHelper {
         BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
         BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
             @Override
             @Override
             public void onReceive(Context context, Intent intent) {
             public void onReceive(Context context, Intent intent) {
-                if (!Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
+                if (connectivityService.getConnectivity().isConnected()) {
                     FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager,
                     FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager,
                                                         accountManager,
                                                         accountManager,
                                                         connectivityService,
                                                         connectivityService,

+ 51 - 14
src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt

@@ -3,7 +3,7 @@
  *
  *
  * @author Chris Narkiewicz
  * @author Chris Narkiewicz
  *
  *
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@ import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.never
 import com.nhaarman.mockitokotlin2.never
 import com.nhaarman.mockitokotlin2.verify
 import com.nhaarman.mockitokotlin2.verify
 import com.nhaarman.mockitokotlin2.whenever
 import com.nhaarman.mockitokotlin2.whenever
+import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Before
@@ -46,7 +47,7 @@ import org.mockito.MockitoAnnotations
 @RunWith(Suite::class)
 @RunWith(Suite::class)
 @Suite.SuiteClasses(
 @Suite.SuiteClasses(
     TestPowerManagementService.PowerSaveMode::class,
     TestPowerManagementService.PowerSaveMode::class,
-    TestPowerManagementService.BatteryCharging::class
+    TestPowerManagementService.Battery::class
 )
 )
 class TestPowerManagementService {
 class TestPowerManagementService {
 
 
@@ -113,7 +114,7 @@ class TestPowerManagementService {
         }
         }
 
 
         @Test
         @Test
-        fun `power save exclusion is available for flagged vendors`() {
+        fun `power saving exclusion is available for flagged vendors`() {
             for (vendor in PowerManagementServiceImpl.OVERLY_AGGRESSIVE_POWER_SAVING_VENDORS) {
             for (vendor in PowerManagementServiceImpl.OVERLY_AGGRESSIVE_POWER_SAVING_VENDORS) {
                 whenever(deviceInfo.vendor).thenReturn(vendor)
                 whenever(deviceInfo.vendor).thenReturn(vendor)
                 assertTrue("Vendor $vendor check failed", powerManagementService.isPowerSavingExclusionAvailable)
                 assertTrue("Vendor $vendor check failed", powerManagementService.isPowerSavingExclusionAvailable)
@@ -121,7 +122,7 @@ class TestPowerManagementService {
         }
         }
 
 
         @Test
         @Test
-        fun `power save exclusion is not available for other vendors`() {
+        fun `power saving exclusion is not available for other vendors`() {
             whenever(deviceInfo.vendor).thenReturn("some_other_nice_vendor")
             whenever(deviceInfo.vendor).thenReturn("some_other_nice_vendor")
             assertFalse(powerManagementService.isPowerSavingExclusionAvailable)
             assertFalse(powerManagementService.isPowerSavingExclusionAvailable)
         }
         }
@@ -142,13 +143,18 @@ class TestPowerManagementService {
         }
         }
     }
     }
 
 
-    class BatteryCharging : Base() {
+    class Battery : Base() {
 
 
-        val mockStickyBatteryStatusIntent: Intent = mock()
+        companion object {
+            const val FULL_RAGE = 32
+            const val HALF_RANGE = 16
+        }
+
+        val intent: Intent = mock()
 
 
         @Before
         @Before
         fun setUp() {
         fun setUp() {
-            whenever(context.registerReceiver(anyOrNull(), anyOrNull())).thenReturn(mockStickyBatteryStatusIntent)
+            whenever(context.registerReceiver(anyOrNull(), anyOrNull())).thenReturn(intent)
         }
         }
 
 
         @Test
         @Test
@@ -166,12 +172,12 @@ class TestPowerManagementService {
             for (row in powerSources) {
             for (row in powerSources) {
                 // WHEN
                 // WHEN
                 //      device is charging using supported power source
                 //      device is charging using supported power source
-                whenever(mockStickyBatteryStatusIntent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
+                whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
                     .thenReturn(row)
                     .thenReturn(row)
 
 
                 // THEN
                 // THEN
                 //      charging flag is true
                 //      charging flag is true
-                assertTrue(powerManagementService.isBatteryCharging)
+                assertTrue(powerManagementService.battery.isCharging)
             }
             }
         }
         }
 
 
@@ -189,12 +195,12 @@ class TestPowerManagementService {
             for (row in powerSources) {
             for (row in powerSources) {
                 // WHEN
                 // WHEN
                 //      device is charging using AC or USB
                 //      device is charging using AC or USB
-                whenever(mockStickyBatteryStatusIntent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
+                whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
                     .thenReturn(row)
                     .thenReturn(row)
 
 
                 // THEN
                 // THEN
                 //      charging flag is true
                 //      charging flag is true
-                assertTrue(powerManagementService.isBatteryCharging)
+                assertTrue(powerManagementService.battery.isCharging)
             }
             }
         }
         }
 
 
@@ -207,13 +213,13 @@ class TestPowerManagementService {
 
 
             // WHEN
             // WHEN
             //      spurious wireless power source is returned
             //      spurious wireless power source is returned
-            whenever(mockStickyBatteryStatusIntent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
+            whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
                 .thenReturn(BatteryManager.BATTERY_PLUGGED_WIRELESS)
                 .thenReturn(BatteryManager.BATTERY_PLUGGED_WIRELESS)
 
 
             // THEN
             // THEN
             //      power source value is ignored on this API level
             //      power source value is ignored on this API level
             //      charging flag is false
             //      charging flag is false
-            assertFalse(powerManagementService.isBatteryCharging)
+            assertFalse(powerManagementService.battery.isCharging)
         }
         }
 
 
         @Test
         @Test
@@ -226,8 +232,39 @@ class TestPowerManagementService {
 
 
             // THEN
             // THEN
             //     charging flag is false
             //     charging flag is false
-            assertFalse(powerManagementService.isBatteryCharging)
+            assertFalse(powerManagementService.battery.isCharging)
             verify(context).registerReceiver(anyOrNull(), any())
             verify(context).registerReceiver(anyOrNull(), any())
         }
         }
+
+        @Test
+        @Suppress("MagicNumber")
+        fun `battery level is available`() {
+            // GIVEN
+            //      battery level info is available
+            //      battery is at 50%
+            whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_LEVEL), any()))
+                .thenReturn(HALF_RANGE)
+            whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_SCALE), any()))
+                .thenReturn(FULL_RAGE)
+
+            // THEN
+            //      battery level is calculated from extra values
+            assertEquals(50, powerManagementService.battery.level)
+        }
+
+        @Test
+        fun `battery level is not available`() {
+            // GIVEN
+            //      battery level is not available
+            val defaultValueArgIndex = 1
+            whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_LEVEL), any()))
+                .thenAnswer { it.getArgument(defaultValueArgIndex) }
+            whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_SCALE), any()))
+                .thenAnswer { it.getArgument(defaultValueArgIndex) }
+
+            // THEN
+            //      battery level is 100
+            assertEquals(BatteryStatus.BATTERY_FULL, powerManagementService.battery.level)
+        }
     }
     }
 }
 }

+ 13 - 3
src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt

@@ -19,6 +19,7 @@
  */
  */
 package com.nextcloud.client.jobs
 package com.nextcloud.client.jobs
 
 
+import android.app.NotificationManager
 import android.content.ContentResolver
 import android.content.ContentResolver
 import android.content.Context
 import android.content.Context
 import android.content.res.Resources
 import android.content.res.Resources
@@ -34,6 +35,7 @@ import com.nextcloud.client.preferences.AppPreferences
 import com.nhaarman.mockitokotlin2.whenever
 import com.nhaarman.mockitokotlin2.whenever
 import com.owncloud.android.datamodel.ArbitraryDataProvider
 import com.owncloud.android.datamodel.ArbitraryDataProvider
 import com.owncloud.android.datamodel.UploadsStorageManager
 import com.owncloud.android.datamodel.UploadsStorageManager
+import org.greenrobot.eventbus.EventBus
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertNull
 import org.junit.Before
 import org.junit.Before
@@ -86,6 +88,12 @@ class BackgroundJobFactoryTest {
     @Mock
     @Mock
     private lateinit var connectivityService: ConnectivityService
     private lateinit var connectivityService: ConnectivityService
 
 
+    @Mock
+    private lateinit var notificationManager: NotificationManager
+
+    @Mock
+    private lateinit var eventBus: EventBus
+
     private lateinit var factory: BackgroundJobFactory
     private lateinit var factory: BackgroundJobFactory
 
 
     @Before
     @Before
@@ -103,12 +111,14 @@ class BackgroundJobFactoryTest {
             resources,
             resources,
             dataProvider,
             dataProvider,
             uploadsStorageManager,
             uploadsStorageManager,
-            connectivityService
+            connectivityService,
+            notificationManager,
+            eventBus
         )
         )
     }
     }
 
 
     @Test
     @Test
-    fun worker_is_created_on_api_level_24() {
+    fun content_observer_worker_is_created_on_api_level_24() {
         // GIVEN
         // GIVEN
         //      api level is > 24
         //      api level is > 24
         //      content URI trigger is supported
         //      content URI trigger is supported
@@ -124,7 +134,7 @@ class BackgroundJobFactoryTest {
     }
     }
 
 
     @Test
     @Test
-    fun worker_is_not_created_below_api_level_24() {
+    fun content_observer_worker_is_not_created_below_api_level_24() {
         // GIVEN
         // GIVEN
         //      api level is < 24
         //      api level is < 24
         //      content URI trigger is not supported
         //      content URI trigger is not supported

+ 39 - 6
src/test/java/com/nextcloud/client/network/ConnectivityServiceTest.kt

@@ -2,7 +2,7 @@
  * Nextcloud Android client application
  * Nextcloud Android client application
  *
  *
  * @author Chris Narkiewicz
  * @author Chris Narkiewicz
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * it under the terms of the GNU Affero General Public License as published by
@@ -35,6 +35,7 @@ import org.apache.commons.httpclient.HttpClient
 import org.apache.commons.httpclient.HttpStatus
 import org.apache.commons.httpclient.HttpStatus
 import org.apache.commons.httpclient.methods.GetMethod
 import org.apache.commons.httpclient.methods.GetMethod
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
 import org.junit.Assert.assertTrue
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Before
 import org.junit.Test
 import org.junit.Test
@@ -47,6 +48,7 @@ import java.net.URI
 
 
 @RunWith(Suite::class)
 @RunWith(Suite::class)
 @Suite.SuiteClasses(
 @Suite.SuiteClasses(
+    ConnectivityServiceTest.Disconnected::class,
     ConnectivityServiceTest.IsConnected::class,
     ConnectivityServiceTest.IsConnected::class,
     ConnectivityServiceTest.WifiConnectionWalledStatusOnLegacyServer::class,
     ConnectivityServiceTest.WifiConnectionWalledStatusOnLegacyServer::class,
     ConnectivityServiceTest.WifiConnectionWalledStatus::class
     ConnectivityServiceTest.WifiConnectionWalledStatus::class
@@ -111,6 +113,7 @@ class ConnectivityServiceTest {
             )
             )
 
 
             whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(networkInfo)
             whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(networkInfo)
+            whenever(platformConnectivityManager.allNetworkInfo).thenReturn(arrayOf(networkInfo))
             whenever(requestBuilder.invoke(any())).thenReturn(getRequest)
             whenever(requestBuilder.invoke(any())).thenReturn(getRequest)
             whenever(clientFactory.createPlainClient()).thenReturn(client)
             whenever(clientFactory.createPlainClient()).thenReturn(client)
             whenever(user.server).thenReturn(newServer)
             whenever(user.server).thenReturn(newServer)
@@ -118,13 +121,32 @@ class ConnectivityServiceTest {
         }
         }
     }
     }
 
 
+    internal class Disconnected : Base() {
+        @Test
+        fun `wifi is disconnected`() {
+            whenever(networkInfo.isConnectedOrConnecting).thenReturn(false)
+            whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
+            connectivityService.connectivity.apply {
+                assertFalse(isConnected)
+                assertTrue(isWifi)
+            }
+        }
+
+        @Test
+        fun `no active network`() {
+            whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(null)
+            assertSame(Connectivity.DISCONNECTED, connectivityService.connectivity)
+        }
+    }
+
     internal class IsConnected : Base() {
     internal class IsConnected : Base() {
 
 
         @Test
         @Test
         fun `connected to wifi`() {
         fun `connected to wifi`() {
             whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
             whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
             whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
             whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
-            assertTrue(connectivityService.isOnlineWithWifi)
+            assertTrue(connectivityService.connectivity.isConnected)
+            assertTrue(connectivityService.connectivity.isWifi)
         }
         }
 
 
         @Test
         @Test
@@ -144,14 +166,21 @@ class ConnectivityServiceTest {
                 )
                 )
             )
             )
             whenever(platformConnectivityManager.allNetworkInfo).thenReturn(wifiNetworkInfoList)
             whenever(platformConnectivityManager.allNetworkInfo).thenReturn(wifiNetworkInfoList)
-            assertTrue(connectivityService.isOnlineWithWifi)
+            connectivityService.connectivity.let {
+                assertTrue(it.isConnected)
+                assertTrue(it.isWifi)
+            }
         }
         }
 
 
         @Test
         @Test
         fun `connected to mobile network`() {
         fun `connected to mobile network`() {
             whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
             whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
             whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_MOBILE)
             whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_MOBILE)
-            assertFalse(connectivityService.isOnlineWithWifi)
+            whenever(platformConnectivityManager.allNetworkInfo).thenReturn(arrayOf(networkInfo))
+            connectivityService.connectivity.let {
+                assertTrue(it.isConnected)
+                assertFalse(it.isWifi)
+            }
         }
         }
     }
     }
 
 
@@ -162,7 +191,9 @@ class ConnectivityServiceTest {
             whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
             whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
             whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
             whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
             whenever(user.server).thenReturn(legacyServer)
             whenever(user.server).thenReturn(legacyServer)
-            assertTrue("Precondition failed", connectivityService.isOnlineWithWifi)
+            assertTrue("Precondition failed", connectivityService.connectivity.let {
+                it.isConnected && it.isWifi
+            })
         }
         }
 
 
         fun mockResponse(maintenance: Boolean = true, httpStatus: Int = HttpStatus.SC_OK) {
         fun mockResponse(maintenance: Boolean = true, httpStatus: Int = HttpStatus.SC_OK) {
@@ -207,7 +238,9 @@ class ConnectivityServiceTest {
             whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
             whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
             whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
             whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
             whenever(accountManager.getServerVersion(any())).thenReturn(OwnCloudVersion.nextcloud_14)
             whenever(accountManager.getServerVersion(any())).thenReturn(OwnCloudVersion.nextcloud_14)
-            assertTrue("Precondition failed", connectivityService.isOnlineWithWifi)
+            assertTrue("Precondition failed", connectivityService.connectivity.let {
+                it.isConnected && it.isWifi
+            })
         }
         }
 
 
         @Test
         @Test