DownloadsFeature.kt 5.83 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.feature.downloads

import android.Manifest.permission.INTERNET
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.annotation.SuppressLint
import android.content.Context
11
12
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
13
import androidx.fragment.app.FragmentManager
14
15
16
17
import mozilla.components.browser.session.Download
import mozilla.components.browser.session.SelectionAwareSessionObserver
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
18
import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.FRAGMENT_TAG
19
20
21
import mozilla.components.feature.downloads.manager.AndroidDownloadManager
import mozilla.components.feature.downloads.manager.DownloadManager
import mozilla.components.feature.downloads.manager.OnDownloadCompleted
22
import mozilla.components.support.base.feature.LifecycleAwareFeature
23
24
import mozilla.components.support.base.feature.OnNeedToRequestPermissions
import mozilla.components.support.base.feature.PermissionsFeature
25
import mozilla.components.support.base.observer.Consumable
26
27
28
import mozilla.components.support.ktx.android.content.isPermissionGranted

/**
29
30
31
 * Feature implementation to provide download functionality for the selected
 * session. The feature will subscribe to the selected session and listen
 * for downloads.
32
 *
33
34
 * @property applicationContext a reference to the application context.
 * @property onNeedToRequestPermissions a callback invoked when permissions
35
36
 * need to be requested before a download can be performed. Once the request
 * is completed, [onPermissionsResult] needs to be invoked.
37
38
39
40
41
42
43
44
 * @property onDownloadCompleted a callback invoked when a download is completed.
 * @property downloadManager a reference to the [DownloadManager] which is
 * responsible for performing the downloads.
 * @property sessionManager a reference to the application's [SessionManager].
 * @property fragmentManager a reference to a [FragmentManager]. If a fragment
 * manager is provided, a dialog will be shown before every download.
 * @property dialog a reference to a [DownloadDialogFragment]. If not provided, an
 * instance of [SimpleDownloadDialogFragment] will be used.
45
46
47
 */
class DownloadsFeature(
    private val applicationContext: Context,
48
    override var onNeedToRequestPermissions: OnNeedToRequestPermissions = { },
49
    var onDownloadCompleted: OnDownloadCompleted = { _, _ -> },
50
    private val downloadManager: DownloadManager = AndroidDownloadManager(applicationContext, onDownloadCompleted),
Tiger Oakes's avatar
Tiger Oakes committed
51
    sessionManager: SessionManager,
52
    private val sessionId: String? = null,
53
    private val fragmentManager: FragmentManager? = null,
54
55
    @VisibleForTesting(otherwise = PRIVATE)
    internal var dialog: DownloadDialogFragment = SimpleDownloadDialogFragment.newInstance()
56
) : SelectionAwareSessionObserver(sessionManager), LifecycleAwareFeature, PermissionsFeature {
57
58

    /**
59
     * Starts observing downloads on the selected session and sends them to the [DownloadManager]
60
61
     * to be processed.
     */
62
    override fun start() {
63
        observeIdOrSelected(sessionId)
64
65
66
67

        findPreviousDialogFragment()?.let {
            reAttachOnStartDownloadListener(it)
        }
68
69
70
    }

    /**
71
     * Stops observing downloads on the selected session.
72
73
74
75
76
77
78
     */
    override fun stop() {
        super.stop()
        downloadManager.unregisterListener()
    }

    /**
79
     * Notifies the [DownloadManager] that a new download must be processed.
80
81
82
83
     */
    @SuppressLint("MissingPermission")
    override fun onDownload(session: Session, download: Download): Boolean {
        return if (applicationContext.isPermissionGranted(INTERNET, WRITE_EXTERNAL_STORAGE)) {
84
85
86
87
88
89
90
            if (fragmentManager != null) {
                showDialog(download, session)
                false
            } else {
                downloadManager.download(download)
                true
            }
91
        } else {
92
            onNeedToRequestPermissions(arrayOf(INTERNET, WRITE_EXTERNAL_STORAGE))
93
94
95
96
97
            false
        }
    }

    /**
98
99
     * Notifies the feature that the permissions request was completed. It will then
     * either trigger or clear the pending download.
100
     */
101
    override fun onPermissionsResult(permissions: Array<String>, grantResults: IntArray) {
102
103
104
105
106
        if (applicationContext.isPermissionGranted(INTERNET, WRITE_EXTERNAL_STORAGE)) {
            activeSession?.let { session ->
                session.download.consume {
                    onDownload(session, it)
                }
107
            }
108
109
        } else {
            activeSession?.download = Consumable.empty()
110
111
        }
    }
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

    @SuppressLint("MissingPermission")
    private fun showDialog(download: Download, session: Session) {
        dialog.setDownload(download)

        dialog.onStartDownload = {
            downloadManager.download(download)
            session.download.consume { true }
        }

        if (!isAlreadyADialogCreated()) {
            dialog.show(fragmentManager, FRAGMENT_TAG)
        }
    }

    private fun isAlreadyADialogCreated(): Boolean {
        return findPreviousDialogFragment() != null
    }

    private fun reAttachOnStartDownloadListener(previousDialog: DownloadDialogFragment?) {
        previousDialog?.apply {
            this@DownloadsFeature.dialog = this
Tiger Oakes's avatar
Tiger Oakes committed
134
            activeSession?.let { session ->
135
136
137
138
                session.download.consume {
                    onDownload(session, it)
                    false
                }
139
140
141
142
143
144
145
            }
        }
    }

    private fun findPreviousDialogFragment(): DownloadDialogFragment? {
        return fragmentManager?.findFragmentByTag(FRAGMENT_TAG) as? DownloadDialogFragment
    }
146
}