//go:build android package notifier import ( "net/netip" "slices" "sort" "sync" "github.com/netbirdio/netbird/client/internal/listener" "github.com/netbirdio/netbird/route" ) type Notifier struct { mu sync.Mutex // currentRoutes is the last announced route set. It exists only to // suppress noise: without it every network map sync would trigger the // Java side, even when the routes did not change. The actual TUN route // state is owned by the route manager and pulled from there. currentRoutes []*route.Route listener listener.NetworkChangeListener } func NewNotifier() *Notifier { return &Notifier{} } func (n *Notifier) SetListener(listener listener.NetworkChangeListener) { n.mu.Lock() defer n.mu.Unlock() n.listener = listener } func (n *Notifier) NotifyRouteChange() { n.mu.Lock() defer n.mu.Unlock() n.notifyLocked() } func (n *Notifier) OnNewRoutes(idMap route.HAMap) { var newRoutes []*route.Route for _, routes := range idMap { for _, r := range routes { if r.IsDynamic() { continue } newRoutes = append(newRoutes, r) } } n.mu.Lock() defer n.mu.Unlock() if !hasRouteDiff(n.currentRoutes, newRoutes) { return } n.currentRoutes = newRoutes n.notifyLocked() } func (n *Notifier) OnNewPrefixes([]netip.Prefix) { // Not used on Android } func (n *Notifier) notifyLocked() { if n.listener == nil { return } n.listener.OnNetworkChanged("") } func (n *Notifier) Close() { // unused } func routesToStrings(routes []*route.Route) []string { nets := make([]string, 0, len(routes)) for _, r := range routes { nets = append(nets, r.NetString()) } return nets } func hasRouteDiff(a []*route.Route, b []*route.Route) bool { as := routesToStrings(a) bs := routesToStrings(b) sort.Strings(as) sort.Strings(bs) return !slices.Equal(as, bs) }