ShareFragment.kt 8.25 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.components.FenixSnackbarPresenter
35
import org.mozilla.fenix.ext.components
36
import org.mozilla.fenix.ext.getRootView
37
import org.mozilla.fenix.ext.requireComponents
38
39
import org.mozilla.fenix.share.listadapters.AppShareOption
import org.mozilla.fenix.share.listadapters.SyncShareOption
40

41
@Suppress("TooManyFunctions")
42
class ShareFragment : AppCompatDialogFragment() {
43
44
45
46
    private lateinit var shareInteractor: ShareInteractor
    private lateinit var shareCloseView: ShareCloseView
    private lateinit var shareToAccountDevicesView: ShareToAccountDevicesView
    private lateinit var shareToAppsView: ShareToAppsView
47
48
    private lateinit var appsListDeferred: Deferred<List<AppShareOption>>
    private lateinit var devicesListDeferred: Deferred<List<SyncShareOption>>
49
    private var connectivityManager: ConnectivityManager? = null
50
51
52
53

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

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

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
        // 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)
        }
    }
74
75
76

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

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
    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()
    }

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

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

123
124
        shareInteractor = ShareInteractor(
            DefaultShareController(
125
                context = requireContext(),
126
                sharedTabs = tabs,
127
                snackbarPresenter = FenixSnackbarPresenter(activity!!.getRootView()!!),
128
                navController = findNavController(),
129
                sendTabUseCases = SendTabUseCases(accountManager),
130
131
132
                dismiss = ::dismiss
            )
        )
133

134
        view.shareWrapper.setOnClickListener { shareInteractor.onShareClosed() }
135
136
        shareToAccountDevicesView =
            ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor)
137
138
        shareCloseView = ShareCloseView(view.closeSharingLayout, shareInteractor)
        shareToAppsView = ShareToAppsView(view.appsShareLayout, shareInteractor)
139

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

    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)
    }

158
159
160
161
    private fun buildAppsList(
        intentActivities: List<ResolveInfo>?,
        context: Context
    ): List<AppShareOption> {
162
163
164
165
166
167
168
        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
169
        }.orEmpty()
170
171
    }

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

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

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

187
188
189
190
191
192
193
194
195
        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) }
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214

            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
    }
215
}
216
217

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