Skip to content

Commit f41b34c

Browse files
Merge pull request #11759 from Isira-Seneviratne/Import-export-worker
Rewrite import and export subscriptions functionality using coroutines
2 parents d1954ba + b6144d0 commit f41b34c

21 files changed

+568
-1023
lines changed

app/build.gradle

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ plugins {
99
alias libs.plugins.kotlin.compose
1010
alias libs.plugins.kotlin.kapt
1111
alias libs.plugins.kotlin.parcelize
12+
alias libs.plugins.kotlinx.serialization
1213
alias libs.plugins.checkstyle
1314
alias libs.plugins.sonarqube
1415
alias libs.plugins.hilt
1516
alias libs.plugins.aboutlibraries
1617
}
1718

1819
android {
19-
compileSdk 34
20+
compileSdk 35
2021
namespace 'org.schabi.newpipe'
2122

2223
defaultConfig {
@@ -226,7 +227,6 @@ dependencies {
226227
implementation libs.androidx.fragment.compose
227228
implementation libs.androidx.lifecycle.livedata
228229
implementation libs.androidx.lifecycle.viewmodel
229-
implementation libs.androidx.localbroadcastmanager
230230
implementation libs.androidx.media
231231
implementation libs.androidx.preference
232232
implementation libs.androidx.recyclerview
@@ -319,6 +319,9 @@ dependencies {
319319
// Scroll
320320
implementation libs.lazycolumnscrollbar
321321

322+
// Kotlinx Serialization
323+
implementation libs.kotlinx.serialization.json
324+
322325
/** Debugging **/
323326
// Memory leak detection
324327
debugImplementation libs.leakcanary.object.watcher

app/proguard-rules.pro

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,18 @@
3434

3535
## For some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
3636
-keep class org.schabi.newpipe.settings.notifications.** { *; }
37+
38+
## Keep Kotlinx Serialization classes
39+
-keepclassmembers class kotlinx.serialization.json.** {
40+
*** Companion;
41+
}
42+
-keepclasseswithmembers class kotlinx.serialization.json.** {
43+
kotlinx.serialization.KSerializer serializer(...);
44+
}
45+
-keep,includedescriptorclasses class org.schabi.newpipe.**$$serializer { *; }
46+
-keepclassmembers class org.schabi.newpipe.** {
47+
*** Companion;
48+
}
49+
-keepclasseswithmembers class org.schabi.newpipe.** {
50+
kotlinx.serialization.KSerializer serializer(...);
51+
}

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
1010
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
1111
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
12+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
1213
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
1314

1415
<!-- We need to be able to open links in the browser on API 30+ -->
@@ -90,8 +91,10 @@
9091
android:exported="false"
9192
android:label="@string/title_activity_about" />
9293

93-
<service android:name=".local.subscription.services.SubscriptionsImportService" />
94-
<service android:name=".local.subscription.services.SubscriptionsExportService" />
94+
<service
95+
android:name="androidx.work.impl.foreground.SystemForegroundService"
96+
android:foregroundServiceType="dataSync"
97+
tools:node="merge" />
9598
<service android:name=".local.feed.service.FeedLoadService" />
9699

97100
<activity

app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
9090
internal abstract fun silentInsertAllInternal(entities: List<SubscriptionEntity>): List<Long>
9191

9292
@Transaction
93-
open fun upsertAll(entities: List<SubscriptionEntity>): List<SubscriptionEntity> {
93+
open fun upsertAll(entities: List<SubscriptionEntity>) {
9494
val insertUidList = silentInsertAllInternal(entities)
9595

9696
insertUidList.forEachIndexed { index: Int, uidFromInsert: Long ->
@@ -106,7 +106,5 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
106106
update(entity)
107107
}
108108
}
109-
110-
return entities
111109
}
112110
}

app/src/main/java/org/schabi/newpipe/local/subscription/ImportConfirmationDialog.java

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,64 @@
33
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
44

55
import android.app.Dialog;
6-
import android.content.Intent;
76
import android.os.Bundle;
87

98
import androidx.annotation.NonNull;
109
import androidx.annotation.Nullable;
1110
import androidx.appcompat.app.AlertDialog;
11+
import androidx.core.os.BundleCompat;
1212
import androidx.fragment.app.DialogFragment;
1313
import androidx.fragment.app.Fragment;
14+
import androidx.work.Constraints;
15+
import androidx.work.ExistingWorkPolicy;
16+
import androidx.work.NetworkType;
17+
import androidx.work.OneTimeWorkRequest;
18+
import androidx.work.OutOfQuotaPolicy;
19+
import androidx.work.WorkManager;
1420

15-
import com.evernote.android.state.State;
1621
import com.livefront.bridge.Bridge;
1722

1823
import org.schabi.newpipe.R;
24+
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportInput;
25+
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportWorker;
1926

2027
public class ImportConfirmationDialog extends DialogFragment {
21-
@State
22-
protected Intent resultServiceIntent;
28+
private static final String INPUT = "input";
2329

24-
public static void show(@NonNull final Fragment fragment,
25-
@NonNull final Intent resultServiceIntent) {
26-
final ImportConfirmationDialog confirmationDialog = new ImportConfirmationDialog();
27-
confirmationDialog.setResultServiceIntent(resultServiceIntent);
30+
public static void show(@NonNull final Fragment fragment, final SubscriptionImportInput input) {
31+
final var confirmationDialog = new ImportConfirmationDialog();
32+
final var arguments = new Bundle();
33+
arguments.putParcelable(INPUT, input);
34+
confirmationDialog.setArguments(arguments);
2835
confirmationDialog.show(fragment.getParentFragmentManager(), null);
2936
}
3037

31-
public void setResultServiceIntent(final Intent resultServiceIntent) {
32-
this.resultServiceIntent = resultServiceIntent;
33-
}
34-
3538
@NonNull
3639
@Override
3740
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
38-
assureCorrectAppLanguage(getContext());
39-
return new AlertDialog.Builder(requireContext())
41+
final var context = requireContext();
42+
assureCorrectAppLanguage(context);
43+
return new AlertDialog.Builder(context)
4044
.setMessage(R.string.import_network_expensive_warning)
4145
.setCancelable(true)
4246
.setNegativeButton(R.string.cancel, null)
4347
.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
44-
if (resultServiceIntent != null && getContext() != null) {
45-
getContext().startService(resultServiceIntent);
46-
}
48+
final var constraints = new Constraints.Builder()
49+
.setRequiredNetworkType(NetworkType.CONNECTED)
50+
.build();
51+
final var input = BundleCompat.getParcelable(requireArguments(), INPUT,
52+
SubscriptionImportInput.class);
53+
54+
final var req = new OneTimeWorkRequest.Builder(SubscriptionImportWorker.class)
55+
.setInputData(input.toData())
56+
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
57+
.setConstraints(constraints)
58+
.build();
59+
60+
WorkManager.getInstance(context)
61+
.enqueueUniqueWork(SubscriptionImportWorker.WORK_NAME,
62+
ExistingWorkPolicy.APPEND_OR_REPLACE, req);
63+
4764
dismiss();
4865
})
4966
.create();
@@ -53,10 +70,6 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
5370
public void onCreate(@Nullable final Bundle savedInstanceState) {
5471
super.onCreate(savedInstanceState);
5572

56-
if (resultServiceIntent == null) {
57-
throw new IllegalStateException("Result intent is null");
58-
}
59-
6073
Bridge.restoreInstanceState(this, savedInstanceState);
6174
}
6275

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package org.schabi.newpipe.local.subscription
33
import android.app.Activity
44
import android.content.Context
55
import android.content.DialogInterface
6-
import android.content.Intent
76
import android.os.Bundle
87
import android.os.Parcelable
98
import android.view.LayoutInflater
@@ -49,11 +48,8 @@ import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
4948
import org.schabi.newpipe.local.subscription.item.GroupsHeader
5049
import org.schabi.newpipe.local.subscription.item.Header
5150
import org.schabi.newpipe.local.subscription.item.ImportSubscriptionsHintPlaceholderItem
52-
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
53-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
54-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
55-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
56-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
51+
import org.schabi.newpipe.local.subscription.workers.SubscriptionExportWorker
52+
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportInput
5753
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard
5854
import org.schabi.newpipe.streams.io.StoredFileHelper
5955
import org.schabi.newpipe.ui.emptystate.setEmptyStateComposable
@@ -224,21 +220,17 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
224220
}
225221

226222
private fun requestExportResult(result: ActivityResult) {
227-
if (result.data != null && result.resultCode == Activity.RESULT_OK) {
228-
activity.startService(
229-
Intent(activity, SubscriptionsExportService::class.java)
230-
.putExtra(SubscriptionsExportService.KEY_FILE_PATH, result.data?.data)
231-
)
223+
val data = result.data?.data
224+
if (data != null && result.resultCode == Activity.RESULT_OK) {
225+
SubscriptionExportWorker.schedule(activity, data)
232226
}
233227
}
234228

235229
private fun requestImportResult(result: ActivityResult) {
236-
if (result.data != null && result.resultCode == Activity.RESULT_OK) {
230+
val data = result.data?.dataString
231+
if (data != null && result.resultCode == Activity.RESULT_OK) {
237232
ImportConfirmationDialog.show(
238-
this,
239-
Intent(activity, SubscriptionsImportService::class.java)
240-
.putExtra(KEY_MODE, PREVIOUS_EXPORT_MODE)
241-
.putExtra(KEY_VALUE, result.data?.data)
233+
this, SubscriptionImportInput.PreviousExportMode(data)
242234
)
243235
}
244236
}

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.schabi.newpipe.local.subscription
22

33
import android.content.Context
4-
import android.util.Pair
54
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
65
import io.reactivex.rxjava3.core.Completable
76
import io.reactivex.rxjava3.core.Flowable
@@ -48,23 +47,16 @@ class SubscriptionManager(context: Context) {
4847
}
4948
}
5049

51-
fun upsertAll(infoList: List<Pair<ChannelInfo, List<ChannelTabInfo>>>): List<SubscriptionEntity> {
52-
val listEntities = subscriptionTable.upsertAll(
53-
infoList.map { SubscriptionEntity.from(it.first) }
54-
)
50+
fun upsertAll(infoList: List<Pair<ChannelInfo, ChannelTabInfo>>) {
51+
val listEntities = infoList.map { SubscriptionEntity.from(it.first) }
52+
subscriptionTable.upsertAll(listEntities)
5553

5654
database.runInTransaction {
5755
infoList.forEachIndexed { index, info ->
58-
info.second.forEach {
59-
feedDatabaseManager.upsertAll(
60-
listEntities[index].uid,
61-
it.relatedItems.filterIsInstance<StreamInfoItem>()
62-
)
63-
}
56+
val streams = info.second.relatedItems.filterIsInstance<StreamInfoItem>()
57+
feedDatabaseManager.upsertAll(listEntities[index].uid, streams)
6458
}
6559
}
66-
67-
return listEntities
6860
}
6961

7062
fun updateChannelInfo(info: ChannelInfo): Completable =

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package org.schabi.newpipe.local.subscription;
22

33
import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.CHANNEL_URL;
4-
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE;
5-
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.INPUT_STREAM_MODE;
6-
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE;
7-
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE;
84

95
import android.app.Activity;
106
import android.content.Intent;
@@ -37,7 +33,7 @@
3733
import org.schabi.newpipe.extractor.NewPipe;
3834
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
3935
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
40-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
36+
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportInput;
4137
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
4238
import org.schabi.newpipe.streams.io.StoredFileHelper;
4339
import org.schabi.newpipe.util.Constants;
@@ -168,10 +164,8 @@ private void onImportClicked() {
168164
}
169165

170166
public void onImportUrl(final String value) {
171-
ImportConfirmationDialog.show(this, new Intent(activity, SubscriptionsImportService.class)
172-
.putExtra(KEY_MODE, CHANNEL_URL_MODE)
173-
.putExtra(KEY_VALUE, value)
174-
.putExtra(Constants.KEY_SERVICE_ID, currentServiceId));
167+
ImportConfirmationDialog.show(this,
168+
new SubscriptionImportInput.ChannelUrlMode(currentServiceId, value));
175169
}
176170

177171
public void onImportFile() {
@@ -186,16 +180,10 @@ public void onImportFile() {
186180
}
187181

188182
private void requestImportFileResult(final ActivityResult result) {
189-
if (result.getData() == null) {
190-
return;
191-
}
192-
193-
if (result.getResultCode() == Activity.RESULT_OK && result.getData().getData() != null) {
183+
final String data = result.getData() != null ? result.getData().getDataString() : null;
184+
if (result.getResultCode() == Activity.RESULT_OK && data != null) {
194185
ImportConfirmationDialog.show(this,
195-
new Intent(activity, SubscriptionsImportService.class)
196-
.putExtra(KEY_MODE, INPUT_STREAM_MODE)
197-
.putExtra(KEY_VALUE, result.getData().getData())
198-
.putExtra(Constants.KEY_SERVICE_ID, currentServiceId));
186+
new SubscriptionImportInput.InputStreamMode(currentServiceId, data));
199187
}
200188
}
201189

0 commit comments

Comments
 (0)