diff --git a/OpenEdXMobile/res/layout/fragment_account.xml b/OpenEdXMobile/res/layout/fragment_account.xml index ecc2076cb0..00de694ce2 100755 --- a/OpenEdXMobile/res/layout/fragment_account.xml +++ b/OpenEdXMobile/res/layout/fragment_account.xml @@ -115,6 +115,29 @@ + + + + + + + %d min %d mins + + + Delete all downloaded videos + + Remove all downloaded videos from your device. + + Are you sure you want to delete all downloaded videos from your device? This action cannot be undone. + + Delete all videos + + Deleting downloaded videos… + + Downloaded videos deleted successfully. + diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/module/analytics/Analytics.java b/OpenEdXMobile/src/main/java/org/edx/mobile/module/analytics/Analytics.java index 0d56b25721..89d0754a68 100644 --- a/OpenEdXMobile/src/main/java/org/edx/mobile/module/analytics/Analytics.java +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/module/analytics/Analytics.java @@ -902,10 +902,12 @@ interface Values { String DO_NOT_SELL_DATA_CLICKED = "edx.bi.app.profile.do_not_sell_data.clicked"; String SUBMIT_FEEDBACK_CLICKED = "edx.bi.app.profile.submit_feedback.clicked"; - // Video Download Quality + // Video Downloads & Quality String PROFILE_VIDEO_DOWNLOAD_QUALITY_CLICKED = "edx.bi.app.profile.video_download_quality.clicked"; String COURSE_VIDEOS_VIDEO_DOWNLOAD_QUALITY_CLICKED = "edx.bi.app.course_videos.video_download_quality.clicked"; String VIDEO_DOWNLOAD_QUALITY_CHANGED = "edx.bi.app.video_download_quality.changed"; + String DELETE_DOWNLOADED_VIDEOS_CLICKED = "edx.bi.app.delete_downloaded_videos.clicked"; + String DELETE_DOWNLOADED_VIDEOS = "edx.bi.app.delete_downloaded_videos"; // Account Registration String REGISTRATION_OPT_IN_TURNED_ON = "edx.bi.app.user.register.opt_in.on"; String REGISTRATION_OPT_IN_TURNED_OFF = "edx.bi.app.user.register.opt_in.off"; @@ -1124,10 +1126,12 @@ interface Events { String DO_NOT_SELL_DATA_CLICKED = "Do Not Sell Data Clicked"; String SUBMIT_FEEDBACK_CLICKED = "Submit feedback clicked"; - // Video Download Quality + // Video Downloads & Quality String PROFILE_VIDEO_DOWNLOAD_QUALITY_CLICKED = "Profile: Video Download Quality Clicked"; String COURSE_VIDEOS_VIDEO_DOWNLOAD_QUALITY_CLICKED = "Course Videos: Video Download Quality Clicked"; String VIDEO_DOWNLOAD_QUALITY_CHANGED = "Video Download Quality Changed"; + String DELETE_DOWNLOADED_VIDEOS_CLICKED = "Delete Downloaded Videos Clicked"; + String DELETE_DOWNLOADED_VIDEOS = "Delete Downloaded Videos"; // Account Registration String REGISTRATION_OPT_IN_TURNED_ON = "Registration: Opt-in Turned On"; String REGISTRATION_OPT_IN_TURNED_OFF = "Registration: Opt-in Turned Off"; diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/module/db/IDatabase.java b/OpenEdXMobile/src/main/java/org/edx/mobile/module/db/IDatabase.java index c4861dc62f..dd86af4de8 100644 --- a/OpenEdXMobile/src/main/java/org/edx/mobile/module/db/IDatabase.java +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/module/db/IDatabase.java @@ -366,12 +366,17 @@ Integer updateDownloadCompleteInfoByDmId(long dmId, VideoModel de, DataCallback callback); /** - * Returns list of all videos from the database. + * Returns list of all videos from the database for a specific username. * * @return */ List getAllVideos(String username, DataCallback> DataCallback); + /** + * Returns list of all videos from the database. + */ + List getAllVideos(DataCallback> DataCallback); + /** * Returns list of all videos from the database for a specific course. * diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/module/db/impl/IDatabaseImpl.java b/OpenEdXMobile/src/main/java/org/edx/mobile/module/db/impl/IDatabaseImpl.java index 606f2556ba..b2dff83adf 100644 --- a/OpenEdXMobile/src/main/java/org/edx/mobile/module/db/impl/IDatabaseImpl.java +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/module/db/impl/IDatabaseImpl.java @@ -580,6 +580,14 @@ public List getAllVideos(String username, return enqueue(op); } + @Override + public List getAllVideos(DataCallback> callback) { + DbOperationGetVideos op = new DbOperationGetVideos(false, DbStructure.Table.DOWNLOADS, null, + null, null, null); + op.setCallback(callback); + return enqueue(op); + } + @Override public List getAllVideosByCourse(@NonNull String courseId, @Nullable DataCallback> callback) { diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/DeleteAllDownloadedVideosEvent.java b/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/DeleteAllDownloadedVideosEvent.java new file mode 100644 index 0000000000..7e29e6c97d --- /dev/null +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/DeleteAllDownloadedVideosEvent.java @@ -0,0 +1,4 @@ +package org.edx.mobile.module.storage; + +public class DeleteAllDownloadedVideosEvent { +} diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/IStorage.java b/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/IStorage.java index cdac434bcf..2bd1e61155 100644 --- a/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/IStorage.java +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/IStorage.java @@ -42,11 +42,18 @@ public interface IStorage { /** * Removes all videos from the database as well as NativeDownloadManager. - * This method fetches all ongoing downloads from the DB; iterates through the list + * This method fetches all downloads from the DB; iterates through the list * and then calls the {@link #removeDownload(VideoModel)} method for each video */ void removeAllDownloads(); + /** + * Removes all videos from the database as well as NativeDownloadManager. + * This method fetches all ongoing downloads from the DB; iterates through the list + * and then calls the {@link #removeDownload(VideoModel)} method for each video + */ + void removeAllOnGoingDownloads(); + /** * This method fetches all unenrolledVideos from the DB. * Iterates through the list and then calls the remove Download method for each video diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/Storage.java b/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/Storage.java index 777c500016..decbf85a9d 100644 --- a/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/Storage.java +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/module/storage/Storage.java @@ -163,6 +163,23 @@ public int removeDownloads(List modelList) { @Override public void removeAllDownloads() { + db.getAllVideos(new DataCallback>() { + @Override + public void onResult(List result) { + removeDownloadsFromApp(result, null); + FileUtil.resetAppDirectory(context); + EventBus.getDefault().post(new DeleteAllDownloadedVideosEvent()); + } + + @Override + public void onFail(Exception ex) { + logger.error(ex); + } + }); + } + + @Override + public void removeAllOnGoingDownloads() { final String username = loginPrefs.getUsername(); final String sha1Username; if (TextUtils.isEmpty(username)) { diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/util/FileUtil.java b/OpenEdXMobile/src/main/java/org/edx/mobile/util/FileUtil.java index 3885d77632..9f97df0c08 100644 --- a/OpenEdXMobile/src/main/java/org/edx/mobile/util/FileUtil.java +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/util/FileUtil.java @@ -334,4 +334,16 @@ public static Uri getFileUriFromMediaStoreUri(@NonNull Context context, @NonNull } return null; } + + /** + * Deletes all the files and directories in the app's external storage directory. + * + * @param context The current context. + */ + public static void resetAppDirectory(@NonNull Context context) { + final File externalAppDir = getExternalAppDir(context); + if (externalAppDir != null) { + deleteRecursive(externalAppDir); + } + } } diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/view/AccountFragment.kt b/OpenEdXMobile/src/main/java/org/edx/mobile/view/AccountFragment.kt index 688b967cc3..080164b6fe 100644 --- a/OpenEdXMobile/src/main/java/org/edx/mobile/view/AccountFragment.kt +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/view/AccountFragment.kt @@ -7,6 +7,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.webkit.URLUtil +import android.widget.Toast +import androidx.annotation.StringRes import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide @@ -38,6 +40,8 @@ import org.edx.mobile.module.analytics.InAppPurchasesAnalytics import org.edx.mobile.module.prefs.FeaturesPrefs import org.edx.mobile.module.prefs.LoginPrefs import org.edx.mobile.module.prefs.UserPrefs +import org.edx.mobile.module.storage.DeleteAllDownloadedVideosEvent +import org.edx.mobile.module.storage.IStorage import org.edx.mobile.user.UserAPI.AccountDataUpdatedCallback import org.edx.mobile.user.UserService import org.edx.mobile.util.AgreementUrlType @@ -94,6 +98,9 @@ class AccountFragment : BaseFragment() { @Inject lateinit var iapAnalytics: InAppPurchasesAnalytics + @Inject + lateinit var storage: IStorage + private val courseViewModel: CourseViewModel by viewModels() private val iapViewModel: InAppPurchasesViewModel by viewModels() @@ -130,6 +137,7 @@ class AccountFragment : BaseFragment() { initPurchases() handleIntentBundle(arguments) initVideoQuality() + initDeleteDownloadedVideos() updateWifiSwitch() updateSDCardSwitch() initHelpFields() @@ -171,7 +179,7 @@ class AccountFragment : BaseFragment() { binding.btnRestorePurchases.setOnClickListener { iapAnalytics.reset() iapAnalytics.trackIAPEvent(Analytics.Events.IAP_RESTORE_PURCHASE_CLICKED) - showLoader() + showLoader(R.string.title_checking_purchases) lifecycleScope.launch { courseViewModel.fetchEnrolledCourses( type = CourseViewModel.CoursesRequestType.STALE, @@ -266,9 +274,9 @@ class AccountFragment : BaseFragment() { fullScreenLoader.show(childFragmentManager, FullscreenLoaderDialogFragment.TAG) } - private fun showLoader() { + private fun showLoader(@StringRes titleResId: Int) { loaderDialog = AlertDialogFragment.newInstance( - R.string.title_checking_purchases, + titleResId, R.layout.alert_dialog_progress ) loaderDialog?.isCancelable = false @@ -301,6 +309,30 @@ class AccountFragment : BaseFragment() { binding.tvVideoDownloadQuality.setText(videoQuality.titleResId) } + private fun initDeleteDownloadedVideos() { + binding.btnDeleteVideos.setOnClickListener { + trackEvent( + Analytics.Events.DELETE_DOWNLOADED_VIDEOS_CLICKED, + Analytics.Values.DELETE_DOWNLOADED_VIDEOS_CLICKED + ) + AlertDialogFragment.newInstance( + getString(R.string.delete_downloaded_videos_title), + getString(R.string.delete_downloaded_videos_dialog_description), + getString(R.string.label_delete), + { _, _ -> + showLoader(R.string.deleting_downloaded_videos_title) + storage.removeAllDownloads() + trackEvent( + Analytics.Events.DELETE_DOWNLOADED_VIDEOS, + Analytics.Values.DELETE_DOWNLOADED_VIDEOS + ) + }, + getString(R.string.label_cancel), + null + ).show(childFragmentManager, null) + } + } + private fun initHelpFields() { if (URLUtil.isValidUrl(featuresPrefs.feedbackFormUrl)) { binding.btnSubmitFeedback.setVisibility(true) @@ -605,6 +637,22 @@ class AccountFragment : BaseFragment() { environment.analyticsRegistry.trackEvent(eventName, biValue) } + @Subscribe(sticky = true) + @Suppress("UNUSED_PARAMETER") + fun onEventMainThread(event: DeleteAllDownloadedVideosEvent) { + binding.root.postDelayed( + { + loaderDialog?.dismiss() + Toast.makeText( + requireContext(), + R.string.deleted_downloaded_videos_success, + Toast.LENGTH_LONG + ).show() + }, 2000 + ) + + } + companion object { @JvmStatic fun newInstance(bundle: Bundle): AccountFragment { diff --git a/OpenEdXMobile/src/main/java/org/edx/mobile/view/Router.java b/OpenEdXMobile/src/main/java/org/edx/mobile/view/Router.java index b5cd63ca7e..6bf2f432f9 100644 --- a/OpenEdXMobile/src/main/java/org/edx/mobile/view/Router.java +++ b/OpenEdXMobile/src/main/java/org/edx/mobile/view/Router.java @@ -369,7 +369,7 @@ public void forceLogout(Context context, AnalyticsRegistry analyticsRegistry, No */ public void performManualLogout(Context context, AnalyticsRegistry analyticsRegistry, NotificationDelegate delegate) { // Remove all ongoing downloads first which requires username - storage.removeAllDownloads(); + storage.removeAllOnGoingDownloads(); loginAPI.logOut(); forceLogout(context, analyticsRegistry, delegate); SecurityUtil.clearUserData(context);