Skip to content

Commit

Permalink
5136: Fix retried uploads stuck in queued state (#5272)
Browse files Browse the repository at this point in the history
* fix stuck uploads

* automate retries for failed uploads once the user returns to the app

* UploadWorker: modify PendingIntent flag and Android version code

* MainActivity: remove automatic retry logic

* Revert "MainActivity: remove automatic retry logic"

* set work request as expedited

* handle notification for foreground service on older versions of Android

* set backoff criteria for work requests

* enqueue failed uploads for a retry

* revert "enqueue failed uploads for a retry"

* limit the number of retries for a failed upload

* add a popup that suggests users to switch to unrestricted battery usage mode

* take users to the battery settings page on the first big upload

* take users to battery optimisation settings page using the standard intent

* add instructions to the battery optimisation settings popup

* remove the first usage of fr.free.nrw.commons from the popup

* comply with the wording in the OS settings

* modify battery optimisation popup instructions, add comments and rename firstBigUploadSet

* add filename to the retry log statement

* update database version

* make battery optimisation dialog appear only on Android 6 and above

* use foreground service instead of setting work request as expedited

* fix retried uploads stuck in queued state

* use MIN_BACKOFF_MILLIS constant instead of using the number 10 and add comments

* factorise the creation of the new OneTimeWorkRequest at one place

* ensure work requests are in accordance with the unit tests

* forbid retries for images which have got uploaded without caption

* add a TODO for the suggestion related to retries

* revert "forbid retries for images which have got uploaded without caption"
  • Loading branch information
RitikaPahwa4444 authored Sep 9, 2023
1 parent 4540f54 commit 81030d1
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 47 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ dependencies {

implementation "androidx.multidex:multidex:$MULTIDEX_VERSION"

def work_version = "2.8.0"
def work_version = "2.8.1"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation("androidx.work:work-runtime:$work_version")
Expand All @@ -168,7 +168,7 @@ project.gradle.taskGraph.whenReady {
}

android {
compileSdkVersion 31
compileSdkVersion 33

defaultConfig {
//applicationId 'fr.free.nrw.commons'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ data class Contribution constructor(
var hasInvalidLocation : Int = 0,
var contentUri: Uri? = null,
var countryCode : String? = null,
var imageSHA1 : String? = null
var imageSHA1 : String? = null,
/**
* Number of times a contribution has been retried after a failure
*/
var retries: Int = 0
) : Parcelable {

fun completeWith(media: Media): Contribution {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public class ContributionsFragment
private static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag";
private MediaDetailPagerFragment mediaDetailPagerFragment;
static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag";
private static final int MAX_RETRIES = 10;

@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
@BindView(R.id.campaigns_view) CampaignView campaignView;
Expand Down Expand Up @@ -593,6 +594,15 @@ public void notifyDataSetChanged() {
}
}

/**
* Restarts the upload process for a contribution
* @param contribution
*/
public void restartUpload(Contribution contribution) {
contribution.setState(Contribution.STATE_QUEUED);
contributionsPresenter.saveContribution(contribution);
Timber.d("Restarting for %s", contribution.toString());
}
/**
* Retry upload when it is failed
*
Expand All @@ -601,10 +611,23 @@ public void notifyDataSetChanged() {
@Override
public void retryUpload(Contribution contribution) {
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
if (contribution.getState() == STATE_FAILED || contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) {
contribution.setState(Contribution.STATE_QUEUED);
contributionsPresenter.saveContribution(contribution);
Timber.d("Restarting for %s", contribution.toString());
if (contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) {
restartUpload(contribution);
} else if (contribution.getState() == STATE_FAILED) {
int retries = contribution.getRetries();
// TODO: Improve UX. Additional details: https://github.com/commons-app/apps-android-commons/pull/5257#discussion_r1304662562
/* Limit the number of retries for a failed upload
to handle cases like invalid filename as such uploads
will never be successful */
if(retries < MAX_RETRIES) {
contribution.setRetries(retries + 1);
Timber.d("Retried uploading %s %d times", contribution.getMedia().getFilename(), retries + 1);
restartUpload(contribution);
} else {
// TODO: Show the exact reason for failure
Toast.makeText(getContext(),
R.string.retry_limit_reached, Toast.LENGTH_SHORT).show();
}
} else {
Timber.d("Skipping re-upload for non-failed %s", contribution.toString());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
package fr.free.nrw.commons.contributions;

import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
import fr.free.nrw.commons.di.CommonsApplicationModule;
import fr.free.nrw.commons.upload.worker.UploadWorker;
import fr.free.nrw.commons.upload.worker.WorkRequestHelper;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;

Expand Down Expand Up @@ -76,11 +70,7 @@ public void saveContribution(Contribution contribution) {
compositeDisposable.add(repository
.save(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe(() -> {
WorkManager.getInstance(view.getContext().getApplicationContext())
.enqueueUniqueWork(
UploadWorker.class.getSimpleName(),
ExistingWorkPolicy.KEEP, OneTimeWorkRequest.from(UploadWorker.class));
}));
.subscribe(() -> WorkRequestHelper.Companion.makeOneTimeWorkRequest(
view.getContext().getApplicationContext(), ExistingWorkPolicy.KEEP)));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.contributions;

import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
Expand All @@ -18,8 +19,6 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
Expand All @@ -44,9 +43,11 @@
import fr.free.nrw.commons.quiz.QuizChecker;
import fr.free.nrw.commons.settings.SettingsFragment;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.upload.worker.UploadWorker;
import fr.free.nrw.commons.upload.worker.WorkRequestHelper;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.schedulers.Schedulers;
import java.util.Collections;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
Expand All @@ -58,6 +59,8 @@ public class MainActivity extends BaseActivity
SessionManager sessionManager;
@Inject
ContributionController controller;
@Inject
ContributionDao contributionDao;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.pager)
Expand Down Expand Up @@ -138,6 +141,9 @@ public void onCreate(Bundle savedInstanceState) {
setTitle(getString(R.string.navigation_item_explore));
setUpLoggedOutPager();
} else {
if (applicationKvStore.getBoolean("firstrun", true)) {
applicationKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", false);
}
if(savedInstanceState == null){
//starting a fresh fragment.
// Open Last opened screen if it is Contributions or Nearby, otherwise Contributions
Expand Down Expand Up @@ -360,6 +366,21 @@ public boolean onOptionsItemSelected(MenuItem item) {
}
}

/**
* Retry all failed uploads as soon as the user returns to the app
*/
@SuppressLint("CheckResult")
private void retryAllFailedUploads() {
contributionDao.
getContribution(Collections.singletonList(Contribution.STATE_FAILED))
.subscribeOn(Schedulers.io())
.subscribe(failedUploads -> {
for (Contribution contribution: failedUploads) {
contributionsFragment.retryUpload(contribution);
}
});
}

public void toggleLimitedConnectionMode() {
defaultKvStore.putBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
!defaultKvStore
Expand All @@ -369,10 +390,8 @@ public void toggleLimitedConnectionMode() {
viewUtilWrapper
.showShortToast(getBaseContext(), getString(R.string.limited_connection_enabled));
} else {
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork(
UploadWorker.class.getSimpleName(),
ExistingWorkPolicy.APPEND_OR_REPLACE, OneTimeWorkRequest.from(UploadWorker.class));

WorkRequestHelper.Companion.makeOneTimeWorkRequest(getApplicationContext(),
ExistingWorkPolicy.APPEND_OR_REPLACE);
viewUtilWrapper
.showShortToast(getBaseContext(), getString(R.string.limited_connection_disabled));
}
Expand Down Expand Up @@ -406,6 +425,8 @@ protected void onResume() {
defaultKvStore.putBoolean("inAppCameraFirstRun", true);
WelcomeActivity.startYourself(this);
}

retryAllFailedUploads();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ open class OnSwipeTouchListener(context: Context?) : View.OnTouchListener {
private val SWIPE_THRESHOLD_WIDTH = (getScreenResolution(context!!)).first / 3
private val SWIPE_VELOCITY_THRESHOLD = 1000

override fun onTouch(view: View?, motionEvent: MotionEvent?): Boolean {
override fun onTouch(view: View?, motionEvent: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(motionEvent)
}

Expand All @@ -32,7 +32,7 @@ open class OnSwipeTouchListener(context: Context?) : View.OnTouchListener {

inner class GestureListener : GestureDetector.SimpleOnGestureListener() {

override fun onDown(e: MotionEvent?): Boolean {
override fun onDown(e: MotionEvent): Boolean {
return true
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao
* The database for accessing the respective DAOs
*
*/
@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class], version = 15, exportSchema = false)
@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class], version = 16, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun contributionDao(): ContributionDao
Expand Down
50 changes: 41 additions & 9 deletions app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package fr.free.nrw.commons.upload;

import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
import static fr.free.nrw.commons.upload.UploadPresenter.COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;

import android.Manifest;
Expand All @@ -10,14 +9,15 @@
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
Expand All @@ -27,8 +27,6 @@
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
Expand All @@ -37,7 +35,6 @@
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
Expand All @@ -52,7 +49,7 @@
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback;
import fr.free.nrw.commons.upload.worker.UploadWorker;
import fr.free.nrw.commons.upload.worker.WorkRequestHelper;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
Expand Down Expand Up @@ -336,9 +333,8 @@ public void updateTopCardTitle() {

@Override
public void makeUploadRequest() {
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork(
UploadWorker.class.getSimpleName(),
ExistingWorkPolicy.APPEND_OR_REPLACE, OneTimeWorkRequest.from(UploadWorker.class));
WorkRequestHelper.Companion.makeOneTimeWorkRequest(getApplicationContext(),
ExistingWorkPolicy.APPEND_OR_REPLACE);
}

@Override
Expand Down Expand Up @@ -383,6 +379,42 @@ private void receiveSharedItems() {
.getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(), uploadableFiles.size()));

fragments = new ArrayList<>();
/* Suggest users to turn battery optimisation off when uploading more than a few files.
That's because we have noticed that many-files uploads have
a much higher probability of failing than uploads with less files.
Show the dialog for Android 6 and above as
the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent was added in API level 23
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (uploadableFiles.size() > 3
&& !defaultKvStore.getBoolean("hasAlreadyLaunchedBigMultiupload")) {
DialogUtil.showAlertDialog(
this,
getString(R.string.unrestricted_battery_mode),
getString(R.string.suggest_unrestricted_mode),
getString(R.string.title_activity_settings),
getString(R.string.cancel),
() -> {
/* Since opening the right settings page might be device dependent, using
https://github.com/WaseemSabir/BatteryPermissionHelper
directly appeared like a promising idea.
However, this simply closed the popup and did not make
the settings page appear on a Pixel as well as a Xiaomi device.
Used the standard intent instead of using this library as
it shows a list of all the apps on the device and allows users to
turn battery optimisation off.
*/
Intent batteryOptimisationSettingsIntent = new Intent(
Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(batteryOptimisationSettingsIntent);
},
() -> {}
);
defaultKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", true);
}
}
for (UploadableFile uploadableFile : uploadableFiles) {
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();

Expand Down
Loading

0 comments on commit 81030d1

Please sign in to comment.