Skip to content

Commit 7c802bd

Browse files
Merge pull request #16934 from nextcloud/warn-auto-upload-battery-saver-mode-distinquish-between-os-and-app
feat(auto-upload): battery saver, batter optimization indication
2 parents 96e63ef + 183b834 commit 7c802bd

19 files changed

Lines changed: 313 additions & 86 deletions

app/src/androidTest/java/com/owncloud/android/AbstractIT.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,11 @@ public Connectivity getConnectivity() {
393393
};
394394

395395
PowerManagementService powerManagementServiceMock = new PowerManagementService() {
396+
@Override
397+
public boolean isIgnoringOptimization() {
398+
return true;
399+
}
400+
396401
@NonNull
397402
@Override
398403
public BatteryStatus getBattery() {

app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ public Connectivity getConnectivity() {
225225
};
226226

227227
PowerManagementService powerManagementServiceMock = new PowerManagementService() {
228+
@Override
229+
public boolean isIgnoringOptimization() {
230+
return true;
231+
}
232+
228233
@NonNull
229234
@Override
230235
public BatteryStatus getBattery() {

app/src/androidTest/java/com/owncloud/android/UploadIT.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ public Connectivity getConnectivity() {
7878
};
7979

8080
private PowerManagementService powerManagementServiceMock = new PowerManagementService() {
81+
@Override
82+
public boolean isIgnoringOptimization() {
83+
return true;
84+
}
85+
8186
@Override
8287
public boolean isPowerSavingEnabled() {
8388
return false;
@@ -226,6 +231,11 @@ public void testUploadOnChargingOnlyButNotCharging() {
226231
@Test
227232
public void testUploadOnChargingOnlyAndCharging() {
228233
PowerManagementService powerManagementServiceMock = new PowerManagementService() {
234+
@Override
235+
public boolean isIgnoringOptimization() {
236+
return true;
237+
}
238+
229239
@Override
230240
public boolean isPowerSavingEnabled() {
231241
return false;

app/src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
4343
}
4444

4545
private val powerManagementServiceMock: PowerManagementService = object : PowerManagementService {
46+
override val isIgnoringOptimization: Boolean
47+
get() = true
48+
4649
override val isPowerSavingEnabled: Boolean
4750
get() = false
4851

app/src/main/java/com/nextcloud/client/device/PowerManagementService.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ interface PowerManagementService {
2121
*/
2222
val isPowerSavingEnabled: Boolean
2323

24+
/**
25+
* Checks app is excluded from battery optimization or not
26+
*/
27+
val isIgnoringOptimization: Boolean
28+
2429
/**
2530
* Checks current battery status using platform [android.os.BatteryManager]
2631
*/

app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ internal class PowerManagementServiceImpl(
2727
}
2828
}
2929

30+
override val isIgnoringOptimization: Boolean
31+
get() {
32+
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
33+
return powerManager.isIgnoringBatteryOptimizations(context.packageName)
34+
}
35+
3036
override val isPowerSavingEnabled: Boolean
3137
get() {
3238
return platformPowerManager.isPowerSaveMode
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.ui.component
9+
10+
import android.annotation.SuppressLint
11+
import android.content.BroadcastReceiver
12+
import android.content.Context
13+
import android.content.Intent
14+
import android.content.IntentFilter
15+
import android.os.PowerManager
16+
import android.provider.Settings
17+
import android.view.View
18+
import androidx.core.net.toUri
19+
import com.nextcloud.client.device.PowerManagementService
20+
import com.nextcloud.utils.extensions.setVisibleIf
21+
import com.owncloud.android.databinding.UploadWarningCardBinding
22+
import com.owncloud.android.utils.theme.ViewThemeUtils
23+
24+
class UploadWarningCard(
25+
private val context: Context,
26+
private val powerManagementService: PowerManagementService,
27+
private val viewThemeUtils: ViewThemeUtils
28+
) {
29+
fun bind(binding: UploadWarningCardBinding) {
30+
val isBatterySaver = powerManagementService.isPowerSavingEnabled
31+
val isIgnoringOptimization = powerManagementService.isIgnoringOptimization
32+
33+
binding.root.setVisibleIf(isBatterySaver || !isIgnoringOptimization)
34+
35+
if (isBatterySaver) {
36+
viewThemeUtils.material.themeCardView(binding.batterySaverLayout)
37+
binding.batterySaverLayout.visibility = View.VISIBLE
38+
binding.batterySaverButton.setOnClickListener {
39+
openBatterySaverPage()
40+
}
41+
} else {
42+
binding.batterySaverLayout.visibility = View.GONE
43+
}
44+
45+
if (!isIgnoringOptimization) {
46+
viewThemeUtils.material.themeCardView(binding.backgroundActivityLimitedLayout)
47+
binding.backgroundActivityLimitedLayout.visibility = View.VISIBLE
48+
binding.backgroundActivityLimitedButton.setOnClickListener {
49+
showIgnoreBatteryOptimizationDialog()
50+
}
51+
} else {
52+
binding.backgroundActivityLimitedLayout.visibility = View.GONE
53+
}
54+
}
55+
56+
// region listen power mode changes
57+
private var binding: UploadWarningCardBinding? = null
58+
59+
private val batterySaverReceiver = object : BroadcastReceiver() {
60+
override fun onReceive(context: Context, intent: Intent) {
61+
if (intent.action == PowerManager.ACTION_POWER_SAVE_MODE_CHANGED) {
62+
binding?.let { bind(it) }
63+
}
64+
}
65+
}
66+
67+
fun register(context: Context, binding: UploadWarningCardBinding) {
68+
this.binding = binding
69+
bind(binding)
70+
context.registerReceiver(batterySaverReceiver, IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED))
71+
}
72+
73+
fun unregister(context: Context) {
74+
context.unregisterReceiver(batterySaverReceiver)
75+
binding = null
76+
}
77+
// endregion
78+
79+
/**
80+
* Opens page for OS's battery saver screen.
81+
*/
82+
private fun openBatterySaverPage() {
83+
context.startActivity(Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS))
84+
}
85+
86+
/**
87+
* Shows dialog to allow background usage for app.
88+
*/
89+
@SuppressLint("BatteryLife")
90+
private fun showIgnoreBatteryOptimizationDialog() {
91+
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
92+
intent.data = "package:${context.packageName}".toUri()
93+
context.startActivity(intent)
94+
}
95+
}

app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.nextcloud.client.jobs.MediaFoldersDetectionWork
3333
import com.nextcloud.client.jobs.NotificationWork
3434
import com.nextcloud.client.jobs.upload.FileUploadWorker
3535
import com.nextcloud.client.preferences.SubFolderRule
36+
import com.nextcloud.ui.component.UploadWarningCard
3637
import com.nextcloud.utils.BatteryOptimizationHelper
3738
import com.nextcloud.utils.extensions.getParcelableArgument
3839
import com.nextcloud.utils.extensions.isDialogFragmentReady
@@ -152,6 +153,8 @@ class SyncedFoldersActivity :
152153
@Inject
153154
lateinit var appInfo: AppInfo
154155

156+
private var uploadWarningCard: UploadWarningCard? = null
157+
155158
lateinit var binding: SyncedFoldersLayoutBinding
156159
lateinit var adapter: SyncedFolderAdapter
157160

@@ -163,6 +166,7 @@ class SyncedFoldersActivity :
163166
super.onCreate(savedInstanceState)
164167
binding = SyncedFoldersLayoutBinding.inflate(layoutInflater)
165168
setContentView(binding.root)
169+
uploadWarningCard = UploadWarningCard(this, powerManagementService, viewThemeUtils)
166170
if (intent != null && intent.extras != null) {
167171
val accountName = intent.extras!!.getString(NotificationWork.KEY_NOTIFICATION_ACCOUNT)
168172
val optionalUser = user
@@ -207,6 +211,7 @@ class SyncedFoldersActivity :
207211
override fun onResume() {
208212
super.onResume()
209213
highlightNavigationViewItem(menuItemId)
214+
uploadWarningCard?.bind(binding.autoUploadBatterySaverWarningCard)
210215
}
211216

212217
fun setupStoragePermissionWarningBanner() {
@@ -256,6 +261,8 @@ class SyncedFoldersActivity :
256261
powerManagementService,
257262
connectivityService
258263
)
264+
uploadWarningCard?.register(this, binding.autoUploadBatterySaverWarningCard)
265+
259266
binding.emptyList.emptyListIcon.setImageResource(R.drawable.nav_synced_folders)
260267
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.emptyList.emptyListViewAction)
261268
val lm = GridLayoutManager(this, gridWidth)
@@ -275,6 +282,11 @@ class SyncedFoldersActivity :
275282
}
276283
}
277284

285+
override fun onDestroy() {
286+
super.onDestroy()
287+
uploadWarningCard?.unregister(this)
288+
}
289+
278290
/**
279291
* loads all media/synced folders, adds them to the recycler view adapter and shows the list.
280292
*

app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.nextcloud.client.jobs.upload.FileUploadEventBroadcaster
2828
import com.nextcloud.client.jobs.upload.FileUploadHelper
2929
import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager
3030
import com.nextcloud.client.utils.Throttler
31+
import com.nextcloud.ui.component.UploadWarningCard
3132
import com.nextcloud.utils.extensions.webDavParentPath
3233
import com.owncloud.android.R
3334
import com.owncloud.android.databinding.UploadListLayoutBinding
@@ -72,6 +73,8 @@ class UploadListActivity :
7273

7374
@Inject lateinit var uploadFileOperationFactory: UploadFileOperationFactory
7475

76+
private var uploadWarningCard: UploadWarningCard? = null
77+
7578
private var swipeListRefreshLayout: SwipeRefreshLayout? = null
7679
private var binding: UploadListLayoutBinding? = null
7780

@@ -87,6 +90,7 @@ class UploadListActivity :
8790
binding = UploadListLayoutBinding.inflate(layoutInflater)
8891
val binding = binding!!
8992
setContentView(binding.getRoot())
93+
uploadWarningCard = UploadWarningCard(this, powerManagementService, viewThemeUtils)
9094
swipeListRefreshLayout = binding.swipeContainingList
9195

9296
// this activity has no file really bound, it's for multiple accounts at the same time; should no inherit
@@ -117,6 +121,10 @@ class UploadListActivity :
117121
adapterHelper
118122
)
119123

124+
binding?.autoUploadBatterySaverWarningCard?.let {
125+
uploadWarningCard?.register(this, it)
126+
}
127+
120128
val lm = GridLayoutManager(this, 1)
121129
uploadListAdapter.setLayoutManager(lm)
122130

@@ -256,6 +264,13 @@ class UploadListActivity :
256264
}
257265
}
258266

267+
override fun onResume() {
268+
super.onResume()
269+
binding?.autoUploadBatterySaverWarningCard?.let {
270+
uploadWarningCard?.bind(it)
271+
}
272+
}
273+
259274
override fun onRemoteOperationFinish(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) {
260275
if (operation !is CheckCurrentCredentialsOperation) {
261276
super.onRemoteOperationFinish(operation, result)
@@ -368,6 +383,11 @@ class UploadListActivity :
368383
}
369384
}
370385

386+
override fun onDestroy() {
387+
super.onDestroy()
388+
uploadWarningCard?.unregister(this)
389+
}
390+
371391
companion object {
372392
private val TAG: String = UploadListActivity::class.java.getSimpleName()
373393

app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,6 @@ class SyncedFolderAdapter(
263263
holder.binding.run {
264264
headerContainer.visibility = View.VISIBLE
265265

266-
if (section == 0) {
267-
autoUploadBatterySaverWarningCard.root.run {
268-
setVisibleIf(powerManagementService.isPowerSavingEnabled)
269-
viewThemeUtils.material.themeCardView(this)
270-
}
271-
}
272-
273266
val syncedFolder = filteredSyncFolderItems[section]
274267

275268
title.text = syncedFolder.folderName

0 commit comments

Comments
 (0)