ShareFragment.kt 6.56 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
import android.os.Bundle
13
import android.os.Parcelable
14
15
16
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
17
import androidx.appcompat.app.AppCompatDialogFragment
18
import androidx.lifecycle.lifecycleScope
19
import androidx.navigation.fragment.findNavController
20
import kotlinx.android.parcel.Parcelize
21
import kotlinx.android.synthetic.main.fragment_share.view.*
22
23
24
25
26
27
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
28
import mozilla.components.feature.sendtab.SendTabUseCases
29
import mozilla.components.service.fxa.manager.FxaAccountManager
30
import org.mozilla.fenix.R
31
import org.mozilla.fenix.ext.components
32
import org.mozilla.fenix.ext.requireComponents
33
34
import org.mozilla.fenix.share.listadapters.AppShareOption
import org.mozilla.fenix.share.listadapters.SyncShareOption
35

36
@Suppress("TooManyFunctions")
37
class ShareFragment : AppCompatDialogFragment() {
38
39
40
41
    interface TabsSharedCallback {
        fun onTabsShared(tabsSize: Int)
    }

42
43
44
45
    private lateinit var shareInteractor: ShareInteractor
    private lateinit var shareCloseView: ShareCloseView
    private lateinit var shareToAccountDevicesView: ShareToAccountDevicesView
    private lateinit var shareToAppsView: ShareToAppsView
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
    private lateinit var appsListDeferred: Deferred<List<AppShareOption>>
    private lateinit var devicesListDeferred: Deferred<List<SyncShareOption>>

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

        // 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)
        }
    }
67
68
69

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
70
        setStyle(STYLE_NO_TITLE, R.style.ShareDialogStyle)
71
72
    }

73
74
75
76
77
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
78
79
        val view = inflater.inflate(R.layout.fragment_share, container, false)
        val args = ShareFragmentArgs.fromBundle(arguments!!)
80
81
82
83
        if (args.url == null && args.tabs.isNullOrEmpty()) {
            throw IllegalStateException("URL and tabs cannot both be null.")
        }

84
        val tabs = args.tabs?.toList() ?: listOf(ShareTab(args.url!!, args.title ?: ""))
85
        val accountManager = requireComponents.backgroundServices.accountManager
86

87
88
89
        shareInteractor = ShareInteractor(
            DefaultShareController(
                fragment = this,
90
                sharedTabs = tabs,
91
                navController = findNavController(),
92
                sendTabUseCases = SendTabUseCases(accountManager),
93
94
95
                dismiss = ::dismiss
            )
        )
96

97
98
99
100
101
        if (isSharingToDevicesAvailable(requireContext().applicationContext)) {
            shareToAccountDevicesView = ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor)
        } else {
            view.devicesShareGroup.visibility = View.GONE
        }
102
103
        shareCloseView = ShareCloseView(view.closeSharingLayout, shareInteractor)
        shareToAppsView = ShareToAppsView(view.appsShareLayout, shareInteractor)
104

105
        return view
106
    }
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

    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 isSharingToDevicesAvailable(context: Context) =
        !context.components.backgroundServices.accountManager.accountNeedsReauth()

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

    private fun buildAppsList(intentActivities: List<ResolveInfo>?, context: Context): List<AppShareOption> {
        return intentActivities?.map { resolveInfo ->
            AppShareOption(
                resolveInfo.loadLabel(context.packageManager).toString(),
                resolveInfo.loadIcon(context.packageManager),
                resolveInfo.activityInfo.packageName,
                resolveInfo.activityInfo.name
            )
        } ?: emptyList()
    }

    private fun buildDeviceList(accountManager: FxaAccountManager): List<SyncShareOption> {
        val list = mutableListOf<SyncShareOption>()

        if (accountManager.authenticatedAccount() == null) {
            list.add(SyncShareOption.SignIn)
            return list
        }

        accountManager.authenticatedAccount()?.deviceConstellation()?.state()?.otherDevices?.let { devices ->
            val shareableDevices = devices.filter { it.capabilities.contains(DeviceCapability.SEND_TAB) }

            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
    }
166
}
167
168

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