ShareFragment.kt 8.1 KB
Newer Older
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
3
4
5
 * 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 org.mozilla.fenix.share
6

7
8
9
10
11
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_SEND
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ResolveInfo
12
13
14
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkRequest
15
import android.os.Bundle
16
import android.os.Parcelable
17
18
19
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
20
import androidx.appcompat.app.AppCompatDialogFragment
21
import androidx.lifecycle.lifecycleScope
22
import androidx.navigation.fragment.findNavController
23
import kotlinx.android.parcel.Parcelize
24
import kotlinx.android.synthetic.main.fragment_share.view.*
25
26
27
28
29
30
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import mozilla.components.concept.sync.DeviceCapability
import mozilla.components.concept.sync.DeviceType
31
import mozilla.components.feature.sendtab.SendTabUseCases
32
import mozilla.components.service.fxa.manager.FxaAccountManager
33
import org.mozilla.fenix.R
34
import org.mozilla.fenix.ext.components
35
import org.mozilla.fenix.ext.requireComponents
36
37
import org.mozilla.fenix.share.listadapters.AppShareOption
import org.mozilla.fenix.share.listadapters.SyncShareOption
38

39
@Suppress("TooManyFunctions")
40
class ShareFragment : AppCompatDialogFragment() {
41
42
43
44
    interface TabsSharedCallback {
        fun onTabsShared(tabsSize: Int)
    }

45
46
47
48
    private lateinit var shareInteractor: ShareInteractor
    private lateinit var shareCloseView: ShareCloseView
    private lateinit var shareToAccountDevicesView: ShareToAccountDevicesView
    private lateinit var shareToAppsView: ShareToAppsView
49
50
    private lateinit var appsListDeferred: Deferred<List<AppShareOption>>
    private lateinit var devicesListDeferred: Deferred<List<SyncShareOption>>
51
    private var connectivityManager: ConnectivityManager? = null
52
53
54
55

    override fun onAttach(context: Context) {
        super.onAttach(context)

56
57
58
59
60
        connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
        val networkRequest = NetworkRequest.Builder().build()
        connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
        // Start preparing the data as soon as we have a valid Context
        appsListDeferred = lifecycleScope.async(Dispatchers.IO) {
            val shareIntent = Intent(ACTION_SEND).apply {
                type = "text/plain"
                flags = FLAG_ACTIVITY_NEW_TASK
            }
            val shareAppsActivities = getIntentActivities(shareIntent, context)
            buildAppsList(shareAppsActivities, context)
        }

        devicesListDeferred = lifecycleScope.async(Dispatchers.IO) {
            val fxaAccountManager = context.components.backgroundServices.accountManager
            buildDeviceList(fxaAccountManager)
        }
    }
76
77
78

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
79
        setStyle(STYLE_NO_TITLE, R.style.ShareDialogStyle)
80
81
    }

82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onLost(network: Network?) {
            reloadDevices()
        }

        override fun onAvailable(network: Network?) {
            reloadDevices()
        }
    }

    private fun reloadDevices() {
        context?.let {
            val fxaAccountManager = it.components.backgroundServices.accountManager
            lifecycleScope.launch {
                val refreshDevicesAsync =
                    fxaAccountManager.authenticatedAccount()?.deviceConstellation()
                        ?.refreshDevicesAsync()
                refreshDevicesAsync?.await()
                val devicesShareOptions = buildDeviceList(fxaAccountManager)
                shareToAccountDevicesView.setSharetargets(devicesShareOptions)
            }
        }
    }

    override fun onDetach() {
        connectivityManager?.unregisterNetworkCallback(networkCallback)
        super.onDetach()
    }

111
112
113
114
115
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
116
117
        val view = inflater.inflate(R.layout.fragment_share, container, false)
        val args = ShareFragmentArgs.fromBundle(arguments!!)
118
119
120
121
        if (args.url == null && args.tabs.isNullOrEmpty()) {
            throw IllegalStateException("URL and tabs cannot both be null.")
        }

Tiger Oakes's avatar
Tiger Oakes committed
122
        val tabs = args.tabs?.toList() ?: listOf(ShareTab(args.url!!, args.title.orEmpty()))
123
        val accountManager = requireComponents.backgroundServices.accountManager
124

125
126
        shareInteractor = ShareInteractor(
            DefaultShareController(
127
                context = requireContext(),
128
                fragment = this,
129
                sharedTabs = tabs,
130
                navController = findNavController(),
131
                sendTabUseCases = SendTabUseCases(accountManager),
132
133
134
                dismiss = ::dismiss
            )
        )
135

136
137
        shareToAccountDevicesView =
            ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor)
138
139
        shareCloseView = ShareCloseView(view.closeSharingLayout, shareInteractor)
        shareToAppsView = ShareToAppsView(view.appsShareLayout, shareInteractor)
140

141
        return view
142
    }
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        lifecycleScope.launch {
            val devicesShareOptions = devicesListDeferred.await()
            shareToAccountDevicesView.setSharetargets(devicesShareOptions)
            val appsToShareTo = appsListDeferred.await()
            shareToAppsView.setSharetargets(appsToShareTo)
        }
    }

    private fun getIntentActivities(shareIntent: Intent, context: Context): List<ResolveInfo>? {
        return context.packageManager.queryIntentActivities(shareIntent, 0)
    }

159
160
161
162
    private fun buildAppsList(
        intentActivities: List<ResolveInfo>?,
        context: Context
    ): List<AppShareOption> {
163
164
165
166
167
168
169
        return intentActivities?.map { resolveInfo ->
            AppShareOption(
                resolveInfo.loadLabel(context.packageManager).toString(),
                resolveInfo.loadIcon(context.packageManager),
                resolveInfo.activityInfo.packageName,
                resolveInfo.activityInfo.name
            )
Tiger Oakes's avatar
Tiger Oakes committed
170
        }.orEmpty()
171
172
    }

173
    @Suppress("ReturnCount")
174
175
176
    private fun buildDeviceList(accountManager: FxaAccountManager): List<SyncShareOption> {
        val list = mutableListOf<SyncShareOption>()

177
178
179
180
181
182
        val activeNetwork = connectivityManager?.activeNetworkInfo
        if (activeNetwork?.isConnected != true) {
            list.add(SyncShareOption.Offline)
            return list
        }

183
184
185
186
187
        if (accountManager.authenticatedAccount() == null) {
            list.add(SyncShareOption.SignIn)
            return list
        }

188
189
190
191
192
193
194
195
196
        if (accountManager.accountNeedsReauth()) {
            list.add(SyncShareOption.Reconnect)
            return list
        }

        accountManager.authenticatedAccount()?.deviceConstellation()?.state()
            ?.otherDevices?.let { devices ->
            val shareableDevices =
                devices.filter { it.capabilities.contains(DeviceCapability.SEND_TAB) }
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215

            if (shareableDevices.isEmpty()) {
                list.add(SyncShareOption.AddNewDevice)
            }

            val shareOptions = shareableDevices.map {
                when (it.deviceType) {
                    DeviceType.MOBILE -> SyncShareOption.Mobile(it.displayName, it)
                    else -> SyncShareOption.Desktop(it.displayName, it)
                }
            }
            list.addAll(shareOptions)

            if (shareableDevices.size > 1) {
                list.add(SyncShareOption.SendAll(shareableDevices))
            }
        }
        return list
    }
216
}
217
218

@Parcelize
219
data class ShareTab(val url: String, val title: String) : Parcelable