|
|
@ -17,17 +17,19 @@ import android.os.Parcelable
|
|
|
|
import android.view.LayoutInflater
|
|
|
|
import android.view.LayoutInflater
|
|
|
|
import android.view.View
|
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
|
|
|
import android.view.ViewGroup
|
|
|
|
|
|
|
|
import androidx.annotation.WorkerThread
|
|
|
|
import androidx.appcompat.app.AppCompatDialogFragment
|
|
|
|
import androidx.appcompat.app.AppCompatDialogFragment
|
|
|
|
|
|
|
|
import androidx.core.content.getSystemService
|
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
|
|
import androidx.navigation.fragment.findNavController
|
|
|
|
import androidx.navigation.fragment.findNavController
|
|
|
|
|
|
|
|
import androidx.navigation.fragment.navArgs
|
|
|
|
import kotlinx.android.parcel.Parcelize
|
|
|
|
import kotlinx.android.parcel.Parcelize
|
|
|
|
import kotlinx.android.synthetic.main.fragment_share.view.*
|
|
|
|
import kotlinx.android.synthetic.main.fragment_share.view.*
|
|
|
|
import kotlinx.coroutines.Deferred
|
|
|
|
import kotlinx.coroutines.Deferred
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.Dispatchers.IO
|
|
|
|
import kotlinx.coroutines.async
|
|
|
|
import kotlinx.coroutines.async
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import mozilla.components.concept.sync.DeviceCapability
|
|
|
|
import mozilla.components.concept.sync.DeviceCapability
|
|
|
|
import mozilla.components.concept.sync.DeviceType
|
|
|
|
|
|
|
|
import mozilla.components.feature.sendtab.SendTabUseCases
|
|
|
|
import mozilla.components.feature.sendtab.SendTabUseCases
|
|
|
|
import mozilla.components.service.fxa.manager.FxaAccountManager
|
|
|
|
import mozilla.components.service.fxa.manager.FxaAccountManager
|
|
|
|
import org.mozilla.fenix.R
|
|
|
|
import org.mozilla.fenix.R
|
|
|
@ -35,7 +37,7 @@ import org.mozilla.fenix.components.FenixSnackbarPresenter
|
|
|
|
import org.mozilla.fenix.ext.components
|
|
|
|
import org.mozilla.fenix.ext.components
|
|
|
|
import org.mozilla.fenix.ext.getRootView
|
|
|
|
import org.mozilla.fenix.ext.getRootView
|
|
|
|
import org.mozilla.fenix.ext.requireComponents
|
|
|
|
import org.mozilla.fenix.ext.requireComponents
|
|
|
|
import org.mozilla.fenix.share.listadapters.AppShareOption
|
|
|
|
import org.mozilla.fenix.share.listadapters.AndroidShareOption
|
|
|
|
import org.mozilla.fenix.share.listadapters.SyncShareOption
|
|
|
|
import org.mozilla.fenix.share.listadapters.SyncShareOption
|
|
|
|
|
|
|
|
|
|
|
|
@Suppress("TooManyFunctions")
|
|
|
|
@Suppress("TooManyFunctions")
|
|
|
@ -44,20 +46,39 @@ class ShareFragment : AppCompatDialogFragment() {
|
|
|
|
private lateinit var shareCloseView: ShareCloseView
|
|
|
|
private lateinit var shareCloseView: ShareCloseView
|
|
|
|
private lateinit var shareToAccountDevicesView: ShareToAccountDevicesView
|
|
|
|
private lateinit var shareToAccountDevicesView: ShareToAccountDevicesView
|
|
|
|
private lateinit var shareToAppsView: ShareToAppsView
|
|
|
|
private lateinit var shareToAppsView: ShareToAppsView
|
|
|
|
private lateinit var appsListDeferred: Deferred<List<AppShareOption>>
|
|
|
|
private lateinit var appsListDeferred: Deferred<List<AndroidShareOption>>
|
|
|
|
private lateinit var devicesListDeferred: Deferred<List<SyncShareOption>>
|
|
|
|
private lateinit var devicesListDeferred: Deferred<List<SyncShareOption>>
|
|
|
|
private var connectivityManager: ConnectivityManager? = null
|
|
|
|
private var connectivityManager: ConnectivityManager? = null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
|
|
|
|
|
|
|
override fun onLost(network: Network?) = reloadDevices()
|
|
|
|
|
|
|
|
override fun onAvailable(network: Network?) = reloadDevices()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun reloadDevices() {
|
|
|
|
|
|
|
|
context?.let { context ->
|
|
|
|
|
|
|
|
val fxaAccountManager = context.components.backgroundServices.accountManager
|
|
|
|
|
|
|
|
lifecycleScope.launch {
|
|
|
|
|
|
|
|
fxaAccountManager.authenticatedAccount()
|
|
|
|
|
|
|
|
?.deviceConstellation()
|
|
|
|
|
|
|
|
?.refreshDevicesAsync()
|
|
|
|
|
|
|
|
?.await()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val devicesShareOptions = buildDeviceList(fxaAccountManager)
|
|
|
|
|
|
|
|
shareToAccountDevicesView.setShareTargets(devicesShareOptions)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onAttach(context: Context) {
|
|
|
|
override fun onAttach(context: Context) {
|
|
|
|
super.onAttach(context)
|
|
|
|
super.onAttach(context)
|
|
|
|
|
|
|
|
|
|
|
|
connectivityManager =
|
|
|
|
connectivityManager = context.getSystemService()
|
|
|
|
context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
|
|
|
|
|
|
|
val networkRequest = NetworkRequest.Builder().build()
|
|
|
|
val networkRequest = NetworkRequest.Builder().build()
|
|
|
|
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
|
|
|
|
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
|
|
|
|
|
|
|
|
|
|
|
|
// Start preparing the data as soon as we have a valid Context
|
|
|
|
// Start preparing the data as soon as we have a valid Context
|
|
|
|
appsListDeferred = lifecycleScope.async(Dispatchers.IO) {
|
|
|
|
appsListDeferred = lifecycleScope.async(IO) {
|
|
|
|
val shareIntent = Intent(ACTION_SEND).apply {
|
|
|
|
val shareIntent = Intent(ACTION_SEND).apply {
|
|
|
|
type = "text/plain"
|
|
|
|
type = "text/plain"
|
|
|
|
flags = FLAG_ACTIVITY_NEW_TASK
|
|
|
|
flags = FLAG_ACTIVITY_NEW_TASK
|
|
|
@ -66,53 +87,29 @@ class ShareFragment : AppCompatDialogFragment() {
|
|
|
|
buildAppsList(shareAppsActivities, context)
|
|
|
|
buildAppsList(shareAppsActivities, context)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
devicesListDeferred = lifecycleScope.async(Dispatchers.IO) {
|
|
|
|
devicesListDeferred = lifecycleScope.async(IO) {
|
|
|
|
val fxaAccountManager = context.components.backgroundServices.accountManager
|
|
|
|
val fxaAccountManager = context.components.backgroundServices.accountManager
|
|
|
|
buildDeviceList(fxaAccountManager)
|
|
|
|
buildDeviceList(fxaAccountManager)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
|
|
setStyle(STYLE_NO_TITLE, R.style.ShareDialogStyle)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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() {
|
|
|
|
override fun onDetach() {
|
|
|
|
connectivityManager?.unregisterNetworkCallback(networkCallback)
|
|
|
|
connectivityManager?.unregisterNetworkCallback(networkCallback)
|
|
|
|
super.onDetach()
|
|
|
|
super.onDetach()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
|
|
setStyle(STYLE_NO_TITLE, R.style.ShareDialogStyle)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreateView(
|
|
|
|
override fun onCreateView(
|
|
|
|
inflater: LayoutInflater,
|
|
|
|
inflater: LayoutInflater,
|
|
|
|
container: ViewGroup?,
|
|
|
|
container: ViewGroup?,
|
|
|
|
savedInstanceState: Bundle?
|
|
|
|
savedInstanceState: Bundle?
|
|
|
|
): View? {
|
|
|
|
): View? {
|
|
|
|
val view = inflater.inflate(R.layout.fragment_share, container, false)
|
|
|
|
val view = inflater.inflate(R.layout.fragment_share, container, false)
|
|
|
|
val args = ShareFragmentArgs.fromBundle(arguments!!)
|
|
|
|
val args by navArgs<ShareFragmentArgs>()
|
|
|
|
check(!(args.url == null && args.tabs.isNullOrEmpty())) { "URL and tabs cannot both be null." }
|
|
|
|
check(!(args.url == null && args.tabs.isNullOrEmpty())) { "URL and tabs cannot both be null." }
|
|
|
|
|
|
|
|
|
|
|
|
val tabs = args.tabs?.toList() ?: listOf(ShareTab(args.url!!, args.title.orEmpty()))
|
|
|
|
val tabs = args.tabs?.toList() ?: listOf(ShareTab(args.url!!, args.title.orEmpty()))
|
|
|
@ -153,74 +150,84 @@ class ShareFragment : AppCompatDialogFragment() {
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Start with some invisible views so the share menu height doesn't jump later
|
|
|
|
|
|
|
|
shareToAppsView.setShareTargets(
|
|
|
|
|
|
|
|
listOf(AndroidShareOption.Invisible, AndroidShareOption.Invisible)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
lifecycleScope.launch {
|
|
|
|
lifecycleScope.launch {
|
|
|
|
val devicesShareOptions = devicesListDeferred.await()
|
|
|
|
val devicesShareOptions = devicesListDeferred.await()
|
|
|
|
shareToAccountDevicesView.setSharetargets(devicesShareOptions)
|
|
|
|
shareToAccountDevicesView.setShareTargets(devicesShareOptions)
|
|
|
|
val appsToShareTo = appsListDeferred.await()
|
|
|
|
val appsToShareTo = appsListDeferred.await()
|
|
|
|
shareToAppsView.setShareTargets(appsToShareTo)
|
|
|
|
shareToAppsView.setShareTargets(appsToShareTo)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@WorkerThread
|
|
|
|
private fun getIntentActivities(shareIntent: Intent, context: Context): List<ResolveInfo>? {
|
|
|
|
private fun getIntentActivities(shareIntent: Intent, context: Context): List<ResolveInfo>? {
|
|
|
|
return context.packageManager.queryIntentActivities(shareIntent, 0)
|
|
|
|
return context.packageManager.queryIntentActivities(shareIntent, 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Returns a list of apps that can be shared to.
|
|
|
|
|
|
|
|
* @param intentActivities List of activities from [getIntentActivities].
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@WorkerThread
|
|
|
|
private fun buildAppsList(
|
|
|
|
private fun buildAppsList(
|
|
|
|
intentActivities: List<ResolveInfo>?,
|
|
|
|
intentActivities: List<ResolveInfo>?,
|
|
|
|
context: Context
|
|
|
|
context: Context
|
|
|
|
): List<AppShareOption> {
|
|
|
|
): List<AndroidShareOption> {
|
|
|
|
return intentActivities?.map { resolveInfo ->
|
|
|
|
return intentActivities
|
|
|
|
AppShareOption(
|
|
|
|
.orEmpty()
|
|
|
|
|
|
|
|
.filter { it.activityInfo.packageName != context.packageName }
|
|
|
|
|
|
|
|
.map { resolveInfo ->
|
|
|
|
|
|
|
|
AndroidShareOption.App(
|
|
|
|
resolveInfo.loadLabel(context.packageManager).toString(),
|
|
|
|
resolveInfo.loadLabel(context.packageManager).toString(),
|
|
|
|
resolveInfo.loadIcon(context.packageManager),
|
|
|
|
resolveInfo.loadIcon(context.packageManager),
|
|
|
|
resolveInfo.activityInfo.packageName,
|
|
|
|
resolveInfo.activityInfo.packageName,
|
|
|
|
resolveInfo.activityInfo.name
|
|
|
|
resolveInfo.activityInfo.name
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}?.filter { it.packageName != context.packageName }.orEmpty()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Suppress("ReturnCount")
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Builds list of options to display in the top row of the share sheet.
|
|
|
|
|
|
|
|
* This will primarily include devices that tabs can be sent to, but also options
|
|
|
|
|
|
|
|
* for reconnecting the account or sending to all devices.
|
|
|
|
|
|
|
|
*/
|
|
|
|
private fun buildDeviceList(accountManager: FxaAccountManager): List<SyncShareOption> {
|
|
|
|
private fun buildDeviceList(accountManager: FxaAccountManager): List<SyncShareOption> {
|
|
|
|
val list = mutableListOf<SyncShareOption>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val activeNetwork = connectivityManager?.activeNetworkInfo
|
|
|
|
val activeNetwork = connectivityManager?.activeNetworkInfo
|
|
|
|
if (activeNetwork?.isConnected != true) {
|
|
|
|
val account = accountManager.authenticatedAccount()
|
|
|
|
list.add(SyncShareOption.Offline)
|
|
|
|
|
|
|
|
return list
|
|
|
|
return when {
|
|
|
|
}
|
|
|
|
// No network
|
|
|
|
|
|
|
|
activeNetwork?.isConnected != true -> listOf(SyncShareOption.Offline)
|
|
|
|
if (accountManager.authenticatedAccount() == null) {
|
|
|
|
// No account signed in
|
|
|
|
list.add(SyncShareOption.SignIn)
|
|
|
|
account == null -> listOf(SyncShareOption.SignIn)
|
|
|
|
return list
|
|
|
|
// Account needs to be re-authenticated
|
|
|
|
}
|
|
|
|
accountManager.accountNeedsReauth() -> listOf(SyncShareOption.Reconnect)
|
|
|
|
|
|
|
|
// Signed in
|
|
|
|
if (accountManager.accountNeedsReauth()) {
|
|
|
|
else -> {
|
|
|
|
list.add(SyncShareOption.Reconnect)
|
|
|
|
val shareableDevices = account.deviceConstellation().state()
|
|
|
|
return list
|
|
|
|
?.otherDevices
|
|
|
|
}
|
|
|
|
.orEmpty()
|
|
|
|
|
|
|
|
.filter { it.capabilities.contains(DeviceCapability.SEND_TAB) }
|
|
|
|
accountManager.authenticatedAccount()?.deviceConstellation()?.state()
|
|
|
|
|
|
|
|
?.otherDevices?.let { devices ->
|
|
|
|
|
|
|
|
val shareableDevices =
|
|
|
|
|
|
|
|
devices.filter { it.capabilities.contains(DeviceCapability.SEND_TAB) }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val list = mutableListOf<SyncShareOption>()
|
|
|
|
if (shareableDevices.isEmpty()) {
|
|
|
|
if (shareableDevices.isEmpty()) {
|
|
|
|
|
|
|
|
// Show add device button if there are no devices
|
|
|
|
list.add(SyncShareOption.AddNewDevice)
|
|
|
|
list.add(SyncShareOption.AddNewDevice)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val shareOptions = shareableDevices.map {
|
|
|
|
shareableDevices.mapTo(list) { SyncShareOption.SingleDevice(it) }
|
|
|
|
when (it.deviceType) {
|
|
|
|
|
|
|
|
DeviceType.MOBILE -> SyncShareOption.Mobile(it.displayName, it)
|
|
|
|
|
|
|
|
else -> SyncShareOption.Desktop(it.displayName, it)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
list.addAll(shareOptions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (shareableDevices.size > 1) {
|
|
|
|
if (shareableDevices.size > 1) {
|
|
|
|
|
|
|
|
// Show send all button if there are multiple devices
|
|
|
|
list.add(SyncShareOption.SendAll(shareableDevices))
|
|
|
|
list.add(SyncShareOption.SendAll(shareableDevices))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
list
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return list
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
companion object {
|
|
|
|