Loading README.md +42 −4 Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ eDrive can also receive some broadcast intent for different purprose: **Force the synchronization.** ```bash adb shell am broadcast -a foundation.e.drive.action.FORCE_SYNC --receiver-include-background adb shell am broadcast -a foundation.e.drive.action.FORCE_SCAN --receiver-include-background ``` **Generate a database dump accessible by the user** Loading @@ -58,6 +58,12 @@ adb shell am broadcast -a foundation.e.drive.action.FORCE_SYNC --receiver-includ adb shell am broadcast -a foundation.e.drive.action.DUMP_DATABASE --receiver-include-background ``` or Download database directly: ```bash adb pull /data/data/foundation.e.drive/databases ``` **Disable log limit on release build** ```bash Loading @@ -69,3 +75,35 @@ adb shell am broadcast -a foundation.e.drive.action.DUMP_DATABASE --receiver-inc ```bash adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable false ``` **Test SyncWorker: upload 10 empty files** Does not rely on FileObserver or FullScanWorker ```bash adb shell am broadcast -a foundation.e.drive.action.TEST_SYNC --receiver-include-background ``` **Display logat** ```bash adb logcat --pid=$(adb shell pidof -s foundation.e.drive) ``` You can also use the script `dev-tools.sh` to run those command. Use : `./dev-tools.sh -h` to display options ### local NC for testing Use following documentation to set up a local NC instance https://gitlab.e.foundation/internal/wiki/-/wikis/qa-team/how-to-run-local-nextcloud `docker run -d -p 8080:80 -e NEXTCLOUD_TRUSTED_DOMAINS="<your computer IP>:8080" -e SQLITE_DATABASE=nc -e NEXTCLOUD_ADMIN_USER=admin -e NEXTCLOUD_ADMIN_PASSWORD=admin nextcloud:<NC version> ` Then you can use Wireshark by example to collect network packet. Example of filter for wireshark: `(ip.src == <computer IP> && ip.dst == <Phone IP>) || (ip.dst == <computer IP> && ip.src == <Phone IP> ) && http` *note: replace <computer IP>, <NC version> and <Phone IP> by values* No newline at end of file app/build.gradle +8 −10 Original line number Diff line number Diff line Loading @@ -3,10 +3,9 @@ plugins { id 'org.jetbrains.kotlin.android' } def versionMajor = 1 def versionMinor = 3 def versionPatch = 16 def versionMinor = 4 def versionPatch = 0 def getTestProp(String propName) { def result = "" Loading Loading @@ -97,16 +96,15 @@ dependencies { implementation 'com.github.nextcloud:android-library:2.13.0' implementation "commons-httpclient:commons-httpclient:3.1@jar" implementation fileTree(include: ['*.jar'], dir: 'libs') api 'androidx.annotation:annotation:1.6.0' implementation 'androidx.core:core:1.10.1' api 'androidx.annotation:annotation:1.7.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.10.0' implementation 'com.github.bumptech.glide:glide:4.15.1' implementation 'com.github.bumptech.glide:annotations:4.15.1' implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.core:core-ktx:1.12.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1' implementation "androidx.work:work-runtime:2.8.1" implementation "androidx.work:work-runtime:2.9.0" implementation 'androidx.test:core:1.5.0' implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'foundation.e:elib:0.0.1-alpha11' Loading @@ -114,7 +112,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.annotation:annotation:1.6.0' androidTestImplementation 'androidx.annotation:annotation:1.7.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'junit:junit:4.13.2' Loading @@ -124,5 +122,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.7.3' testImplementation 'org.mockito:mockito-core:5.0.0' testImplementation 'androidx.work:work-testing:2.8.1' testImplementation 'androidx.work:work-testing:2.9.0' } app/src/main/AndroidManifest.xml +7 −2 Original line number Diff line number Diff line Loading @@ -15,6 +15,11 @@ <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/> <uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <uses-permission android:name="foundation.e.pwaplayer.provider.READ_WRITE" /> <uses-permission Loading @@ -36,7 +41,6 @@ android:allowBackup="true" android:icon="@drawable/ic_murena_eel" android:label="@string/app_name" android:persistent="true" android:roundIcon="@drawable/ic_murena_eel" android:requestLegacyExternalStorage="true"> <!-- required to access media folder since app target sdk is 29+ but deprecated. Remove when Q support will be dropped--> <activity Loading Loading @@ -97,9 +101,10 @@ android:exported="true" tools:ignore="ExportedReceiver"> <intent-filter> <action android:name="foundation.e.drive.action.FORCE_SYNC" /> <action android:name="foundation.e.drive.action.FORCE_SCAN" /> <action android:name="foundation.e.drive.action.DUMP_DATABASE"/> <action android:name="foundation.e.drive.action.FULL_LOG_ON_PROD"/> <action android:name="foundation.e.drive.action.TEST_SYNC"/> </intent-filter> </receiver> Loading app/src/main/java/foundation/e/drive/account/AccountUtils.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2022-2023 * 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 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package foundation.e.drive.account import android.accounts.Account import android.accounts.AccountManager import android.content.Context import foundation.e.drive.R import foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME import foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS object AccountUtils { @JvmStatic fun getPremiumPlan(accountManager: AccountManager, account: Account?): String? { if (account == null) return null val groupData = accountManager.getUserData(account, ACCOUNT_DATA_GROUPS) val premiumGroup = extractPremiumGroup(groupData) return extractPremiumPlan(premiumGroup) } @JvmStatic private fun extractPremiumPlan(premiumGroup: String?): String? { if (premiumGroup.isNullOrEmpty()) return null val splitPremiumGroup = premiumGroup.split("-") return if (splitPremiumGroup.size < 2) null else splitPremiumGroup[1] } @JvmStatic private fun extractPremiumGroup(groupData: String?): String? { if (groupData.isNullOrEmpty()) return null val groups = groupData.split(",") return groups.firstOrNull { group: String -> group.contains("premium-") } } @JvmStatic fun getAccount(context: Context): Account? { val prefs = context.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) val accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") return getAccount(accountName!!, context) } @JvmStatic fun getAccount(accountName: String, context: Context): Account? { if (accountName.isEmpty()) return null val accountManager = AccountManager.get(context) val accountType = context.getString(R.string.eelo_account_type) return accountManager.getAccountsByType(accountType) .firstOrNull { account -> account.name == accountName } } } app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt +7 −7 Original line number Diff line number Diff line Loading @@ -15,12 +15,13 @@ import android.content.SharedPreferences import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import foundation.e.drive.R import foundation.e.drive.account.AccountUtils import foundation.e.drive.models.SyncedFolder import foundation.e.drive.utils.AppConstants import foundation.e.drive.utils.CommonUtils import foundation.e.drive.utils.DavClientProvider import foundation.e.drive.utils.RootSyncedFolderProvider import foundation.e.drive.work.WorkRequestFactory.* import foundation.e.drive.work.WorkerUtils import timber.log.Timber /** Loading Loading @@ -51,7 +52,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { if (registerSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(context)) WorkerUtils.enqueuePeriodicUserInfoFetching(WorkManager.getInstance(context)) } } Loading Loading @@ -80,7 +81,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { return false } if (!isExistingAccount(accountName, accountType, context)) { if (!isExistingAccount(accountName, context)) { Timber.w("No account exist for username: %s ", accountType, accountName) return false } Loading @@ -96,9 +97,8 @@ class AccountAddedReceiver() : BroadcastReceiver() { return prefs.getBoolean(AppConstants.SETUP_COMPLETED, false) } private fun isExistingAccount(accountName: String, accountType: String, context: Context): Boolean { val account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(context)) return account != null private fun isExistingAccount(accountName: String, context: Context): Boolean { return AccountUtils.getAccount(accountName, context) != null } private fun registerSetupWorkers(context: Context): Boolean { Loading @@ -118,7 +118,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { val rootSyncedFolderList: List<SyncedFolder> = RootSyncedFolderProvider.getSyncedFolderRoots(context) if (rootSyncedFolderList.isNullOrEmpty()) { if (rootSyncedFolderList.isEmpty()) { return null } Loading Loading
README.md +42 −4 Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ eDrive can also receive some broadcast intent for different purprose: **Force the synchronization.** ```bash adb shell am broadcast -a foundation.e.drive.action.FORCE_SYNC --receiver-include-background adb shell am broadcast -a foundation.e.drive.action.FORCE_SCAN --receiver-include-background ``` **Generate a database dump accessible by the user** Loading @@ -58,6 +58,12 @@ adb shell am broadcast -a foundation.e.drive.action.FORCE_SYNC --receiver-includ adb shell am broadcast -a foundation.e.drive.action.DUMP_DATABASE --receiver-include-background ``` or Download database directly: ```bash adb pull /data/data/foundation.e.drive/databases ``` **Disable log limit on release build** ```bash Loading @@ -69,3 +75,35 @@ adb shell am broadcast -a foundation.e.drive.action.DUMP_DATABASE --receiver-inc ```bash adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable false ``` **Test SyncWorker: upload 10 empty files** Does not rely on FileObserver or FullScanWorker ```bash adb shell am broadcast -a foundation.e.drive.action.TEST_SYNC --receiver-include-background ``` **Display logat** ```bash adb logcat --pid=$(adb shell pidof -s foundation.e.drive) ``` You can also use the script `dev-tools.sh` to run those command. Use : `./dev-tools.sh -h` to display options ### local NC for testing Use following documentation to set up a local NC instance https://gitlab.e.foundation/internal/wiki/-/wikis/qa-team/how-to-run-local-nextcloud `docker run -d -p 8080:80 -e NEXTCLOUD_TRUSTED_DOMAINS="<your computer IP>:8080" -e SQLITE_DATABASE=nc -e NEXTCLOUD_ADMIN_USER=admin -e NEXTCLOUD_ADMIN_PASSWORD=admin nextcloud:<NC version> ` Then you can use Wireshark by example to collect network packet. Example of filter for wireshark: `(ip.src == <computer IP> && ip.dst == <Phone IP>) || (ip.dst == <computer IP> && ip.src == <Phone IP> ) && http` *note: replace <computer IP>, <NC version> and <Phone IP> by values* No newline at end of file
app/build.gradle +8 −10 Original line number Diff line number Diff line Loading @@ -3,10 +3,9 @@ plugins { id 'org.jetbrains.kotlin.android' } def versionMajor = 1 def versionMinor = 3 def versionPatch = 16 def versionMinor = 4 def versionPatch = 0 def getTestProp(String propName) { def result = "" Loading Loading @@ -97,16 +96,15 @@ dependencies { implementation 'com.github.nextcloud:android-library:2.13.0' implementation "commons-httpclient:commons-httpclient:3.1@jar" implementation fileTree(include: ['*.jar'], dir: 'libs') api 'androidx.annotation:annotation:1.6.0' implementation 'androidx.core:core:1.10.1' api 'androidx.annotation:annotation:1.7.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.10.0' implementation 'com.github.bumptech.glide:glide:4.15.1' implementation 'com.github.bumptech.glide:annotations:4.15.1' implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.core:core-ktx:1.12.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1' implementation "androidx.work:work-runtime:2.8.1" implementation "androidx.work:work-runtime:2.9.0" implementation 'androidx.test:core:1.5.0' implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'foundation.e:elib:0.0.1-alpha11' Loading @@ -114,7 +112,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.annotation:annotation:1.6.0' androidTestImplementation 'androidx.annotation:annotation:1.7.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'junit:junit:4.13.2' Loading @@ -124,5 +122,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.7.3' testImplementation 'org.mockito:mockito-core:5.0.0' testImplementation 'androidx.work:work-testing:2.8.1' testImplementation 'androidx.work:work-testing:2.9.0' }
app/src/main/AndroidManifest.xml +7 −2 Original line number Diff line number Diff line Loading @@ -15,6 +15,11 @@ <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/> <uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <uses-permission android:name="foundation.e.pwaplayer.provider.READ_WRITE" /> <uses-permission Loading @@ -36,7 +41,6 @@ android:allowBackup="true" android:icon="@drawable/ic_murena_eel" android:label="@string/app_name" android:persistent="true" android:roundIcon="@drawable/ic_murena_eel" android:requestLegacyExternalStorage="true"> <!-- required to access media folder since app target sdk is 29+ but deprecated. Remove when Q support will be dropped--> <activity Loading Loading @@ -97,9 +101,10 @@ android:exported="true" tools:ignore="ExportedReceiver"> <intent-filter> <action android:name="foundation.e.drive.action.FORCE_SYNC" /> <action android:name="foundation.e.drive.action.FORCE_SCAN" /> <action android:name="foundation.e.drive.action.DUMP_DATABASE"/> <action android:name="foundation.e.drive.action.FULL_LOG_ON_PROD"/> <action android:name="foundation.e.drive.action.TEST_SYNC"/> </intent-filter> </receiver> Loading
app/src/main/java/foundation/e/drive/account/AccountUtils.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2022-2023 * 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 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package foundation.e.drive.account import android.accounts.Account import android.accounts.AccountManager import android.content.Context import foundation.e.drive.R import foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME import foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS object AccountUtils { @JvmStatic fun getPremiumPlan(accountManager: AccountManager, account: Account?): String? { if (account == null) return null val groupData = accountManager.getUserData(account, ACCOUNT_DATA_GROUPS) val premiumGroup = extractPremiumGroup(groupData) return extractPremiumPlan(premiumGroup) } @JvmStatic private fun extractPremiumPlan(premiumGroup: String?): String? { if (premiumGroup.isNullOrEmpty()) return null val splitPremiumGroup = premiumGroup.split("-") return if (splitPremiumGroup.size < 2) null else splitPremiumGroup[1] } @JvmStatic private fun extractPremiumGroup(groupData: String?): String? { if (groupData.isNullOrEmpty()) return null val groups = groupData.split(",") return groups.firstOrNull { group: String -> group.contains("premium-") } } @JvmStatic fun getAccount(context: Context): Account? { val prefs = context.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) val accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") return getAccount(accountName!!, context) } @JvmStatic fun getAccount(accountName: String, context: Context): Account? { if (accountName.isEmpty()) return null val accountManager = AccountManager.get(context) val accountType = context.getString(R.string.eelo_account_type) return accountManager.getAccountsByType(accountType) .firstOrNull { account -> account.name == accountName } } }
app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt +7 −7 Original line number Diff line number Diff line Loading @@ -15,12 +15,13 @@ import android.content.SharedPreferences import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import foundation.e.drive.R import foundation.e.drive.account.AccountUtils import foundation.e.drive.models.SyncedFolder import foundation.e.drive.utils.AppConstants import foundation.e.drive.utils.CommonUtils import foundation.e.drive.utils.DavClientProvider import foundation.e.drive.utils.RootSyncedFolderProvider import foundation.e.drive.work.WorkRequestFactory.* import foundation.e.drive.work.WorkerUtils import timber.log.Timber /** Loading Loading @@ -51,7 +52,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { if (registerSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(context)) WorkerUtils.enqueuePeriodicUserInfoFetching(WorkManager.getInstance(context)) } } Loading Loading @@ -80,7 +81,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { return false } if (!isExistingAccount(accountName, accountType, context)) { if (!isExistingAccount(accountName, context)) { Timber.w("No account exist for username: %s ", accountType, accountName) return false } Loading @@ -96,9 +97,8 @@ class AccountAddedReceiver() : BroadcastReceiver() { return prefs.getBoolean(AppConstants.SETUP_COMPLETED, false) } private fun isExistingAccount(accountName: String, accountType: String, context: Context): Boolean { val account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(context)) return account != null private fun isExistingAccount(accountName: String, context: Context): Boolean { return AccountUtils.getAccount(accountName, context) != null } private fun registerSetupWorkers(context: Context): Boolean { Loading @@ -118,7 +118,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { val rootSyncedFolderList: List<SyncedFolder> = RootSyncedFolderProvider.getSyncedFolderRoots(context) if (rootSyncedFolderList.isNullOrEmpty()) { if (rootSyncedFolderList.isEmpty()) { return null } Loading