ShareFragment.kt 6.51 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
28
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
import mozilla.components.service.fxa.manager.FxaAccountManager
29
import org.mozilla.fenix.R
30
import org.mozilla.fenix.ext.components
31
import org.mozilla.fenix.ext.requireComponents
32
33
import org.mozilla.fenix.share.listadapters.AppShareOption
import org.mozilla.fenix.share.listadapters.SyncShareOption
34

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

41
42
43
44
    private lateinit var shareInteractor: ShareInteractor
    private lateinit var shareCloseView: ShareCloseView
    private lateinit var shareToAccountDevicesView: ShareToAccountDevicesView
    private lateinit var shareToAppsView: ShareToAppsView
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    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)
        }
    }
66
67
68

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

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

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

86
87
88
89
90
91
92
93
94
        shareInteractor = ShareInteractor(
            DefaultShareController(
                fragment = this,
                tabs = tabs,
                navController = findNavController(),
                account = account,
                dismiss = ::dismiss
            )
        )
95

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

104
        return view
105
    }
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

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

@Parcelize
data class ShareTab(val url: String, val title: String, val sessionId: String? = null) : Parcelable