/* * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.wireguard.android.fragment import android.os.Bundle import android.util.Log; import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.view.MenuProvider import androidx.databinding.DataBindingUtil import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import com.wireguard.android.R import com.wireguard.android.backend.Tunnel import com.wireguard.android.databinding.TunnelDetailFragmentBinding import com.wireguard.android.databinding.TunnelDetailPeerBinding import com.wireguard.android.model.ObservableTunnel import com.wireguard.android.util.QuantityFormatter import com.wireguard.android.viewmodel.ConfigDetail import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.time.Duration import java.time.LocalDateTime import java.time.ZoneId /** * Fragment that shows details about a specific tunnel. */ class TunnelDetailFragment : BaseFragment(), MenuProvider { private var binding: TunnelDetailFragmentBinding? = null private var lastState = Tunnel.State.TOGGLE private var timerActive = true override fun onMenuItemSelected(menuItem: MenuItem): Boolean { return false } override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.tunnel_detail, menu) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) binding = TunnelDetailFragmentBinding.inflate(inflater, container, false) binding?.executePendingBindings() return binding?.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) } override fun onDestroyView() { binding = null super.onDestroyView() } override fun onResume() { super.onResume() timerActive = true lifecycleScope.launch { while (timerActive) { updateStats() delay(1000) } } } override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) { val binding = binding ?: return binding.tunnel = newTunnel if (newTunnel == null) { binding.config = null } else { lifecycleScope.launch { try { var config = newTunnel.getConfigDetailAsync() binding.config = config Log.i(TAG, "onSelectedTunnelChanged " + config + ", " + config.config) } catch (_: Throwable) { binding.config = null } } } lastState = Tunnel.State.TOGGLE lifecycleScope.launch { updateStats() } } override fun onStop() { timerActive = false super.onStop() } override fun onViewStateRestored(savedInstanceState: Bundle?) { binding ?: return binding!!.fragment = this onSelectedTunnelChanged(null, selectedTunnel) super.onViewStateRestored(savedInstanceState) } private suspend fun updateStats() { val binding = binding ?: return val tunnel = binding.tunnel ?: return if (!isResumed) return val state = tunnel.state if (state != Tunnel.State.UP && lastState == state) return lastState = state var now = LocalDateTime.now(ZoneId.of("UTC")) try { val statistics = tunnel.getStatisticsAsync() for (i in 0 until binding.peersLayout.childCount) { val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i)) ?: continue val publicKey = peer.item!!.publicKey val rx = statistics.peerRx(publicKey) val tx = statistics.peerTx(publicKey) if (rx == 0L && tx == 0L) { peer.transferLabel.visibility = View.GONE peer.transferText.visibility = View.GONE } else { peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx)) peer.transferLabel.visibility = View.VISIBLE peer.transferText.visibility = View.VISIBLE } val lastHandshake:LocalDateTime? = statistics.peerLastHandshake(publicKey) if (lastHandshake == null) { peer.lastHandshakeLabel.visibility = View.GONE peer.lastHandshakeText.visibility = View.GONE } else { peer.lastHandshakeText.text = getString(R.string.last_handshake_ago, QuantityFormatter.formatDuration(Duration.between(lastHandshake, now))) peer.lastHandshakeLabel.visibility = View.VISIBLE peer.lastHandshakeText.visibility = View.VISIBLE } } } catch (e: Throwable) { for (i in 0 until binding.peersLayout.childCount) { val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i)) ?: continue peer.transferLabel.visibility = View.GONE peer.transferText.visibility = View.GONE peer.lastHandshakeLabel.visibility = View.GONE peer.lastHandshakeText.visibility = View.GONE } } } companion object { private const val TAG = "WireGuard/TunnelDetailFragment" } }