Delaying upload if device is in power save mode

This change adds handling for uploads from device when it is operating in power save mode and is a fix for #1277. In those case, uploads are behaving similar as if upload requires Wi-Fi (or that device needs to be charging) and those conditions are not met - they are delayed until device exits power save mode. Same thing holds true for contacts backup and explicit uploads - they are not delayed. Difference with Wi-Fi (and charging) conditions is in that 1) this delay cannot be set per synced folder and 2) it is turned on by default. For devices that don't have this capability (API level < 21) - old behavior (backward-compatible) is used; they behave as this check is not present.

Jobs for file uploads are skipped if they are executing during power save mode. Only exception to this is when user is doing "force resync" which will continue even in power save mode.

Also added is listener for power save mode, albeit it makes sense only if user explicitly turns off power save mode while it is in it, as putting device to be charged will restart all jobs already by design (another listener that already exists).
This commit is contained in:
Branko Kokanovic 2017-09-09 13:35:40 +02:00 committed by AndyScherzinger
parent 9e7e2c11c6
commit 8bc432027e
No known key found for this signature in database
GPG key ID: 6CADC7E3523C308B
12 changed files with 140 additions and 44 deletions

View file

@ -194,6 +194,10 @@ public class MainApp extends MultiDexApplication {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
ReceiversHelper.registerPowerChangeReceiver();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ReceiversHelper.registerPowerSaveReceiver();
}
}
public static Context getAppContext() {

View file

@ -383,6 +383,8 @@ public class UploadsStorageManager extends Observable {
"==" + UploadResult.LOCK_FAILED.getValue() +
" OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
"==" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
" OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
"==" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
" AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
new String[]{account.name}
);
@ -424,12 +426,14 @@ public class UploadsStorageManager extends Observable {
if (account != null) {
return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.LOCK_FAILED.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
new String[]{account.name}
);
@ -446,9 +450,13 @@ public class UploadsStorageManager extends Observable {
public OCUpload[] getFailedButNotDelayedUploads() {
return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() + AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(),
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue(),
null
);
}
@ -465,14 +473,15 @@ public class UploadsStorageManager extends Observable {
result = getDB().delete(
ProviderTableMeta.CONTENT_URI_UPLOADS,
ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
AND +
ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.LOCK_FAILED.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
new String[]{account.name}
);
}
@ -512,17 +521,19 @@ public class UploadsStorageManager extends Observable {
whereArgs[0] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value);
whereArgs[1] = String.valueOf(UploadStatus.UPLOAD_FAILED.value);
whereArgs[2] = account.name;
result = getDB().delete(
long result = getDB().delete(
ProviderTableMeta.CONTENT_URI_UPLOADS,
ProviderTableMeta.UPLOADS_STATUS + "=? OR " + ProviderTableMeta.UPLOADS_STATUS + "=?" +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
AND +
ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
AND +
ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", whereArgs
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.LOCK_FAILED.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
"<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
whereArgs
);
}

View file

@ -36,7 +36,8 @@ public enum UploadResult {
SERVICE_INTERRUPTED(10),
DELAYED_FOR_CHARGING(11),
MAINTENANCE_MODE(12),
LOCK_FAILED(13);
LOCK_FAILED(13),
DELAYED_IN_POWER_SAVE_MODE(14);
private final int value;
@ -80,6 +81,8 @@ public enum UploadResult {
return MAINTENANCE_MODE;
case 13:
return LOCK_FAILED;
case 14:
return DELAYED_IN_POWER_SAVE_MODE;
}
return null;
}
@ -116,6 +119,8 @@ public enum UploadResult {
return DELAYED_FOR_WIFI;
case DELAYED_FOR_CHARGING:
return DELAYED_FOR_CHARGING;
case DELAYED_IN_POWER_SAVE_MODE:
return DELAYED_IN_POWER_SAVE_MODE;
case UNKNOWN_ERROR:
if (result.getException() instanceof java.io.FileNotFoundException) {
return FILE_ERROR;

View file

@ -73,6 +73,7 @@ import com.owncloud.android.ui.activity.UploadListActivity;
import com.owncloud.android.ui.notifications.NotificationUtils;
import com.owncloud.android.utils.ErrorMessageAdapter;
import com.owncloud.android.utils.ThemeUtils;
import com.owncloud.android.utils.UploadUtils;
import java.io.File;
import java.util.AbstractList;
@ -924,6 +925,11 @@ public class FileUploader extends Service
!Device.isCharging(MainApp.getAppContext())) {
cancel(mCurrentUpload.getAccount().name, mCurrentUpload.getFile().getRemotePath()
, ResultCode.DELAYED_FOR_CHARGING);
} else if (
!mCurrentUpload.getIsIgnoringPowerSaveMode() &&
UploadUtils.isPowerSaveMode(MainApp.getAppContext())) {
cancel(mCurrentUpload.getAccount().name, mCurrentUpload.getFile().getRemotePath()
, ResultCode.DELAYED_IN_POWER_SAVE_MODE);
}
}
}
@ -1142,6 +1148,7 @@ public class FileUploader extends Service
!ResultCode.LOCAL_FILE_NOT_FOUND.equals(uploadResult.getCode()) &&
!uploadResult.getCode().equals(ResultCode.DELAYED_FOR_WIFI) &&
!uploadResult.getCode().equals(ResultCode.DELAYED_FOR_CHARGING) &&
!uploadResult.getCode().equals(ResultCode.DELAYED_IN_POWER_SAVE_MODE) &&
!uploadResult.getCode().equals(ResultCode.LOCK_FAILED) ) {
int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker :

View file

@ -47,6 +47,7 @@ import com.owncloud.android.ui.activity.Preferences;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.FilesSyncHelper;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.UploadUtils;
import java.io.File;
import java.io.IOException;
@ -66,6 +67,7 @@ public class FilesSyncJob extends Job {
public static final String TAG = "FilesSyncJob";
public static final String SKIP_CUSTOM = "skipCustom";
public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving";
@NonNull
@Override
@ -81,6 +83,12 @@ public class FilesSyncJob extends Job {
PersistableBundleCompat bundle = params.getExtras();
final boolean skipCustom = bundle.getBoolean(SKIP_CUSTOM, false);
final boolean overridePowerSaving = bundle.getBoolean(OVERRIDE_POWER_SAVING, false);
// If we are in power save mode, better to postpone upload
if (UploadUtils.isPowerSaveMode(context) && !overridePowerSaving) {
return Result.SUCCESS;
}
Resources resources = MainApp.getAppContext().getResources();
boolean lightVersion = resources.getBoolean(R.bool.syncedFolder_light);

View file

@ -29,6 +29,7 @@ import android.support.annotation.RequiresApi;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.owncloud.android.utils.FilesSyncHelper;
import com.owncloud.android.utils.UploadUtils;
import java.util.concurrent.TimeUnit;
@ -43,7 +44,8 @@ public class NContentObserverJob extends JobService {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (params.getJobId() == FilesSyncHelper.ContentSyncJobId && params.getTriggeredContentAuthorities()
!= null && params.getTriggeredContentUris() != null
&& params.getTriggeredContentUris().length > 0) {
&& params.getTriggeredContentUris().length > 0
&& !UploadUtils.isPowerSaveMode(getApplicationContext())) {
PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat();
persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true);
@ -51,6 +53,7 @@ public class NContentObserverJob extends JobService {
new JobRequest.Builder(FilesSyncJob.TAG)
.setExecutionWindow(1, TimeUnit.SECONDS.toMillis(2))
.setBackoffCriteria(TimeUnit.SECONDS.toMillis(5), JobRequest.BackoffPolicy.LINEAR)
.setExtras(persistableBundleCompat)
.setUpdateCurrent(false)
.build()
.schedule();

View file

@ -48,6 +48,7 @@ import com.owncloud.android.operations.common.SyncOperation;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.MimeType;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.UploadUtils;
import com.owncloud.android.utils.UriUtils;
import org.apache.commons.httpclient.HttpStatus;
@ -102,6 +103,7 @@ public class UploadFileOperation extends SyncOperation {
private int mCreatedBy = CREATED_BY_USER;
private boolean mOnWifiOnly = false;
private boolean mWhileChargingOnly = false;
private boolean mIgnoringPowerSaveMode = false;
private boolean mWasRenamed = false;
private long mOCUploadId = -1;
@ -192,6 +194,8 @@ public class UploadFileOperation extends SyncOperation {
mOCUploadId = upload.getUploadId();
mCreatedBy = upload.getCreadtedBy();
mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
// Ignore power save mode only if user explicitly created this upload
mIgnoringPowerSaveMode = (mCreatedBy == CREATED_BY_USER);
}
public boolean getIsWifiRequired() {
@ -202,6 +206,8 @@ public class UploadFileOperation extends SyncOperation {
return mWhileChargingOnly;
}
public boolean getIsIgnoringPowerSaveMode() { return mIgnoringPowerSaveMode; }
public Account getAccount() {
return mAccount;
}
@ -347,6 +353,12 @@ public class UploadFileOperation extends SyncOperation {
return new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
}
// Check that device is not in power save mode
if (!mIgnoringPowerSaveMode && UploadUtils.isPowerSaveMode(mContext)) {
Log_OC.d(TAG, "Upload delayed because device is in power save mode: " + getRemotePath());
return new RemoteOperationResult(ResultCode.DELAYED_IN_POWER_SAVE_MODE);
}
/// check if the file continues existing before schedule the operation
if (!originalFile.exists()) {
Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");

View file

@ -43,6 +43,7 @@ import android.view.View;
import android.widget.Toast;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
@ -237,9 +238,12 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
break;
case R.id.action_force_rescan:
PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat();
persistableBundleCompat.putBoolean(FilesSyncJob.OVERRIDE_POWER_SAVING, true);
new JobRequest.Builder(FilesSyncJob.TAG)
.setExact(1_000L)
.setUpdateCurrent(false)
.setExtras(persistableBundleCompat)
.build()
.schedule();

View file

@ -571,6 +571,10 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
case LOCK_FAILED:
status = mParentActivity.getString(R.string.lock_failed);
break;
case DELAYED_IN_POWER_SAVE_MODE:
status = mParentActivity.getString(
R.string.uploads_view_upload_status_waiting_exit_power_save_mode);
break;
default:
status = "Naughty devs added a new fail result but no description for the user";
break;

View file

@ -71,4 +71,22 @@ public class ReceiversHelper {
context.registerReceiver(broadcastReceiver, intentFilter);
}
public static void registerPowerSaveReceiver() {
Context context = MainApp.getAppContext();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.os.action.POWER_SAVE_MODE_CHANGED");
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!UploadUtils.isPowerSaveMode(context)) {
FilesSyncHelper.restartJobsIfNeeded();
}
}
};
context.registerReceiver(broadcastReceiver, intentFilter);
}
}

View file

@ -19,12 +19,15 @@
package com.owncloud.android.utils;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.State;
import android.os.BatteryManager;
import android.os.Build;
import android.os.PowerManager;
public class UploadUtils {
@ -49,4 +52,20 @@ public class UploadUtils {
&& cm.getActiveNetworkInfo().getState() == State.CONNECTED;
}
/**
* Checks if device is in power save mode. For older devices that do not support this API, returns false.
* @return true if it is, false otherwise.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean isPowerSaveMode(Context context)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return powerManager.isPowerSaveMode();
}
// For older versions, we just say that device is not in power save mode
return false;
}
}

View file

@ -172,6 +172,7 @@
<string name="uploads_view_upload_status_service_interrupted">App terminated</string>
<string name="uploads_view_upload_status_unknown_fail">Unknown error</string>
<string name="uploads_view_upload_status_waiting_for_wifi">Waiting for Wi-Fi connectivity</string>
<string name="uploads_view_upload_status_waiting_exit_power_save_mode">Waiting to exit power save mode</string>
<string name="uploads_view_later_waiting_to_upload">Waiting to upload</string>
<string name="uploads_view_group_header" translatable="false">%1$s (%2$d)</string>
<string name="downloader_download_in_progress_ticker">Downloading &#8230;</string>