All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jupnp.android.AndroidRouter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2011-2025 4th Line GmbH, Switzerland and others
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License Version 1 or later
 * ("CDDL") (collectively, the "License"). You may not use this file
 * except in compliance with the License. See LICENSE.txt for more
 * information.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * SPDX-License-Identifier: CDDL-1.0
 */
package org.jupnp.android;

import org.jupnp.UpnpServiceConfiguration;
import org.jupnp.model.ModelUtil;
import org.jupnp.protocol.ProtocolFactory;
import org.jupnp.transport.RouterException;
import org.jupnp.transport.RouterImpl;
import org.jupnp.transport.spi.InitializationException;
import org.jupnp.util.Exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;

/**
 * Monitors all network connectivity changes, switching the router accordingly.
 *
 * @author Michael Pujos
 * @author Christian Bauer
 */
public class AndroidRouter extends RouterImpl {

    private final Logger logger = LoggerFactory.getLogger(AndroidRouter.class);

    private final Context context;

    private final WifiManager wifiManager;
    protected WifiManager.MulticastLock multicastLock;
    protected WifiManager.WifiLock wifiLock;
    protected NetworkInfo networkInfo;
    protected BroadcastReceiver broadcastReceiver;

    public AndroidRouter(UpnpServiceConfiguration configuration, ProtocolFactory protocolFactory, Context context)
            throws InitializationException {
        super(configuration, protocolFactory);

        this.context = context;
        this.wifiManager = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE));
        this.networkInfo = NetworkUtils.getConnectedNetworkInfo(context);

        // Only register for network connectivity changes if we are not running on emulator
        if (!ModelUtil.ANDROID_EMULATOR) {
            this.broadcastReceiver = createConnectivityBroadcastReceiver();
            context.registerReceiver(broadcastReceiver, new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"));
        }
    }

    protected BroadcastReceiver createConnectivityBroadcastReceiver() {
        return new ConnectivityBroadcastReceiver();
    }

    @Override
    protected int getLockTimeoutMillis() {
        return 15000;
    }

    @Override
    public void shutdown() throws RouterException {
        super.shutdown();
        unregisterBroadcastReceiver();
    }

    @Override
    public boolean enable() throws RouterException {
        lock(writeLock);
        try {
            boolean enabled = super.enable();
            // Enable multicast on the WiFi network interface, requires android.permission.CHANGE_WIFI_MULTICAST_STATE
            if (enabled && isWifi()) {
                setWiFiMulticastLock(true);
                setWifiLock(true);
            }
            return enabled;
        } finally {
            unlock(writeLock);
        }
    }

    @Override
    public boolean disable() throws RouterException {
        lock(writeLock);
        try {
            // Disable multicast on WiFi network interface, requires android.permission.CHANGE_WIFI_MULTICAST_STATE
            if (isWifi()) {
                setWiFiMulticastLock(false);
                setWifiLock(false);
            }
            return super.disable();
        } finally {
            unlock(writeLock);
        }
    }

    public NetworkInfo getNetworkInfo() {
        return networkInfo;
    }

    public boolean isMobile() {
        return NetworkUtils.isMobile(networkInfo);
    }

    public boolean isWifi() {
        return NetworkUtils.isWifi(networkInfo);
    }

    public boolean isEthernet() {
        return NetworkUtils.isEthernet(networkInfo);
    }

    public boolean enableWiFi() {
        logger.info("Enabling WiFi...");
        try {
            return wifiManager.setWifiEnabled(true);
        } catch (Exception e) {
            // workaround (HTC One X, 4.0.3)
            // java.lang.SecurityException: Permission Denial: writing com.android.providers.settings.SettingsProvider
            // uri content://settings/system from pid=4691, uid=10226 requires android.permission.WRITE_SETTINGS
            // at android.os.Parcel.readException(Parcel.java:1332)
            // at android.os.Parcel.readException(Parcel.java:1286)
            // at android.net.wifi.IWifiManager$Stub$Proxy.setWifiEnabled(IWifiManager.java:1115)
            // at android.net.wifi.WifiManager.setWifiEnabled(WifiManager.java:946)
            logger.warn("SetWifiEnabled failed", e);
            return false;
        }
    }

    public void unregisterBroadcastReceiver() {
        if (broadcastReceiver != null) {
            context.unregisterReceiver(broadcastReceiver);
            broadcastReceiver = null;
        }
    }

    protected void setWiFiMulticastLock(boolean enable) {
        if (multicastLock == null) {
            multicastLock = wifiManager.createMulticastLock(getClass().getSimpleName());
        }

        if (enable) {
            if (multicastLock.isHeld()) {
                logger.warn("WiFi multicast lock already acquired");
            } else {
                logger.info("WiFi multicast lock acquired");
                multicastLock.acquire();
            }
        } else {
            if (multicastLock.isHeld()) {
                logger.info("WiFi multicast lock released");
                multicastLock.release();
            } else {
                logger.warn("WiFi multicast lock already released");
            }
        }
    }

    protected void setWifiLock(boolean enable) {
        if (wifiLock == null) {
            wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, getClass().getSimpleName());
        }

        if (enable) {
            if (wifiLock.isHeld()) {
                logger.warn("WiFi lock already acquired");
            } else {
                logger.info("WiFi lock acquired");
                wifiLock.acquire();
            }
        } else {
            if (wifiLock.isHeld()) {
                logger.info("WiFi lock released");
                wifiLock.release();
            } else {
                logger.warn("WiFi lock already released");
            }
        }
    }

    /**
     * Can be overridden by subclasses to do additional work.
     *
     * @param oldNetwork null when first called by constructor.
     */
    protected void onNetworkTypeChange(NetworkInfo oldNetwork, NetworkInfo newNetwork) throws RouterException {
        logger.info("Network type changed {} => {}", oldNetwork == null ? "" : oldNetwork.getTypeName(),
                newNetwork == null ? "NONE" : newNetwork.getTypeName());

        if (disable()) {
            logger.info("Disabled router on network type change (old network: {})",
                    oldNetwork == null ? "NONE" : oldNetwork.getTypeName());
        }

        networkInfo = newNetwork;
        if (enable()) {
            // Can return false (via earlier InitializationException thrown by NetworkAddressFactory) if
            // no bindable network address found!
            logger.info("Enabled router on network type change (new network: {})",
                    newNetwork == null ? "NONE" : newNetwork.getTypeName());
        }
    }

    /**
     * Handles errors when network has been switched, during reception of
     * network switch broadcast. Logs a warning by default, override to
     * change this behavior.
     */
    protected void handleRouterExceptionOnNetworkTypeChange(RouterException e) {
        Throwable cause = Exceptions.unwrap(e);
        if (cause instanceof InterruptedException) {
            logger.info("Router was interrupted", e);
        } else {
            logger.warn("Router error on network change", e);
        }
    }

    class ConnectivityBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (!intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                return;
            }

            displayIntentInfo(intent);

            NetworkInfo newNetworkInfo = NetworkUtils.getConnectedNetworkInfo(context);

            // When Android switches WiFI => MOBILE, sometimes we may have a short transition
            // with no network: WIFI => NONE, NONE => MOBILE
            // The code below attempts to make it look like a single WIFI => MOBILE
            // transition, retrying up to 3 times getting the current network.
            //
            // Note: this can block the UI thread for up to 3s
            if (networkInfo != null && newNetworkInfo == null) {
                for (int i = 1; i <= 3; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        return;
                    }
                    logger.warn("{} => NONE network transition, waiting for new network... retry #{}",
                            networkInfo.getTypeName(), i);
                    newNetworkInfo = NetworkUtils.getConnectedNetworkInfo(context);
                    if (newNetworkInfo != null) {
                        break;
                    }
                }
            }

            if (isSameNetworkType(networkInfo, newNetworkInfo)) {
                logger.info("No actual network change... ignoring event!");
            } else {
                try {
                    onNetworkTypeChange(networkInfo, newNetworkInfo);
                } catch (RouterException e) {
                    handleRouterExceptionOnNetworkTypeChange(e);
                }
            }
        }

        protected boolean isSameNetworkType(NetworkInfo network1, NetworkInfo network2) {
            if (network1 == null && network2 == null) {
                return true;
            }
            if (network1 == null || network2 == null) {
                return false;
            }
            return network1.getType() == network2.getType();
        }

        protected void displayIntentInfo(Intent intent) {
            boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
            String reason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
            boolean isFailover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);

            NetworkInfo currentNetworkInfo = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
            NetworkInfo otherNetworkInfo = intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);

            logger.info("Connectivity change detected...");
            logger.info("EXTRA_NO_CONNECTIVITY: {}", noConnectivity);
            logger.info("EXTRA_REASON: {}", reason);
            logger.info("EXTRA_IS_FAILOVER: {}", isFailover);
            logger.info("EXTRA_NETWORK_INFO: {}", currentNetworkInfo == null ? "none" : currentNetworkInfo);
            logger.info("EXTRA_OTHER_NETWORK_INFO: {}", otherNetworkInfo == null ? "none" : otherNetworkInfo);
            logger.info("EXTRA_EXTRA_INFO: {}", intent.getStringExtra(ConnectivityManager.EXTRA_EXTRA_INFO));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy