Forzare l’uso della connessione mobile (3G o GPRS) con il WiFi in Android

In Android, l’attivazione della connessione WiFi porta immediatamente alla disattivazione delle altre connessioni, compresa quella mobile, e tutto il traffico dati viene instradato su di essa. Assieme ad una serie di vantaggi, come l’indubbio risparmio per connessioni dati a consumo e il guadagno di velocita’, questo comportampo porta ad alcuni problemi, principalmente:

  • La sospensione senza preavviso di connessioni gia’ in piedi sulla rete dati mobile
  • L’impossibilita’ di accedere a quei servizi legati alla SIM. Ad esempio, un operatori telefonico potrebbe effettuano un accredito direttamente all’utente basandosi sulla SIM che genera il traffico. Con il Wifi, non si puo’ ottenere questa informazione.

Quindi, per quanto l’utilita’ di forzare l’uso della connessione dati mobile per raggiungere un certo indirizzo all’interno della proprio applicazione possa sembrare scontata, purtroppo Android non mette a disposizione nessuna API documentata e sicura per farlo, ma occorre passare per una serie di workaround che funzionano solo dalla versione 2.2.

/**
 * Enable mobile connection for a specific address
 * @param context a Context (application or activity)
 * @param address the address to enable
 * @return true for success, else false
 */
private boolean forceMobileConnectionForAddress(Context context, String address) {
    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    if (null == connectivityManager) {
        Log.debug(TAG_LOG, "ConnectivityManager is null, cannot try to force a mobile connection");
        return false;
    }
 
    //check if mobile connection is available and connected
    State state = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
    Log.debug(TAG_LOG, "TYPE_MOBILE_HIPRI network state: " + state);
    if (0 == state.compareTo(State.CONNECTED) || 0 == state.compareTo(State.CONNECTING)) {
        return true;
    }
    
    //activate mobile connection in addition to other connection already activated
    int resultInt = connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, "enableHIPRI");
    Log.debug(TAG_LOG, "startUsingNetworkFeature for enableHIPRI result: " + resultInt);
    
    //-1 means errors
    // 0 means already enabled
    // 1 means enabled
    // other values can be returned, because this method is vendor specific
    if (-1 == resultInt) {
        Log.error(TAG_LOG, "Wrong result of startUsingNetworkFeature, maybe problems");
        return false;
    }
    if (0 == resultInt) {
        Log.debug(TAG_LOG, "No need to perform additional network settings");
        return true;
    }
    
    //find the host name to route
    String hostName = StringUtil.extractAddressFromUrl(address);
    Log.debug(TAG_LOG, "Source address: " + address);
    Log.debug(TAG_LOG, "Destination host address to route: " + hostName);
    if (TextUtils.isEmpty(hostName)) hostName = address;
     
    //create a route for the specified address
    int hostAddress = lookupHost(hostName);
    if (-1 == hostAddress) {
        Log.error(TAG_LOG, "Wrong host address transformation, result was -1");
        return false;
    }
    //wait some time needed to connection manager for waking up
    try {
        for (int counter=0; counter<10; counter++) {
            State checkState = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
            if (0 == checkState.compareTo(State.CONNECTED))
                break;
            Thread.sleep(1000);
        }
    } catch (InterruptedException e) {
        //nothing to do
    }
    boolean resultBool = connectivityManager.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_HIPRI, hostAddress);
    Log.debug(TAG_LOG, "requestRouteToHost result: " + resultBool);
    if (!resultBool)
        Log.error(TAG_LOG, "Wrong requestRouteToHost result: expected true, but was false");

    return resultBool;
}

In pratica, se non lo e' gia', viene attivata la connessione mobile grazie alla chiamata
startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, "enableHIPRI")
per poi creare una route all'indirizzo da raggiungere che forza il passaggio sulla connessione mobile
requestRouteToHost(ConnectivityManager.TYPE_MOBILE_HIPRI, hostAddress)


E qui c'e' la funzione che permette di calcolare l'intero che corrisponde all'indirizzo da contattare

/**
 * This method extracts from address the hostname
 * @param url eg. http://some.where.com:8080/sync
 * @return some.where.com
 */
public static String extractAddressFromUrl(String url) {
    String urlToProcess = null;

    //find protocol
    int protocolEndIndex = url.indexOf("://");
    if(protocolEndIndex>0) {
        urlToProcess = url.substring(protocolEndIndex + 3);
    } else {
        urlToProcess = url;
    }

    // If we have port number in the address we strip everything
    // after the port number
    int pos = urlToProcess.indexOf(':');
    if (pos >= 0) {
        urlToProcess = urlToProcess.substring(0, pos);
    }

    // If we have resource location in the address then we strip
    // everything after the '/'
    pos = urlToProcess.indexOf('/');
    if (pos >= 0) {
        urlToProcess = urlToProcess.substring(0, pos);
    }

    // If we have ? in the address then we strip
    // everything after the '?'
    pos = urlToProcess.indexOf('?');
    if (pos >= 0) {
        urlToProcess = urlToProcess.substring(0, pos);
    }
    return urlToProcess;
}

/**
 * Transform host name in int value used by {@link ConnectivityManager.requestRouteToHost}
 * method
 * 
 * @param hostname
 * @return -1 if the host doesn't exists, elsewhere its translation
 * to an integer
 */
private static int lookupHost(String hostname) {
    InetAddress inetAddress;
    try {
        inetAddress = InetAddress.getByName(hostname);
    } catch (UnknownHostException e) {
        return -1;
    }
    byte[] addrBytes;
    int addr;
    addrBytes = inetAddress.getAddress();
    addr = ((addrBytes[3] & 0xff) << 24)
            | ((addrBytes[2] & 0xff) << 16)
            | ((addrBytes[1] & 0xff) << 8)
            |  (addrBytes[0] & 0xff);
    return addr;
}

In ultimo, occorre aggiungere questi permessi all'AndroidManifest.xml



Con questo trucchetto si riesce ad indirizzare tutto il traffico verso uno o piu' indirizzi specifici. Ho notato che dopo 15-20 secondi di inattivita', la connessione mobile viene disattivata di nuovo.

Il codice e' testato su un Nexus One e su un LG Optimus ma, dato che le implementazioni sono specifiche dei vendor, non garantisco che funzioni su tutti gli altri device con Android, dalla 2.2 in su.

8 Comments

  1. Gran bella guida! Ma se volessi deregistrare un URL inserito in RequestRouteToHost? C’e un modo??

  2. Non appena la connessione 3g si chiude automaticamente (passati 20-30 secondi di inutilizzo),la route impostata viene cancellata.

    Se invece la wifi e’ accesa, tutte le connessioni passaranno di li…

  3. Io riesco a mantenerla accesa sempre attraverso un timer, mi serviva un modo per deregistrare un URL mantenendo accesa l’antenna!Ti ringrazio comunque.

  4. Se hai anche la connessione wifi abilitata, tutto il traffico passa per la wifi, quindi non occorre “deregistrare” qualcosa… O magari sono io che non ho capito…

  5. Supponi di avere “www.prova1.it” registrato sull’HIPRI, in questo caso avresti il traffico verso e da questo sito attraverso la connessione 3G e non Wifi (supponendo che sia acceso). Supponi adesso che io voglia registrare http://www.prova2.it, no problem, registro e ho due URL (IP) che sono registrati. Il traffico verso questi due siti passerà attraverso la 3G. Supponi ora che voglia fare passare il traffico sulla 3G solo per la connessione verso prova1 e non più verso prova2, non mi basta spegnere l’HIPRI, altrimenti perderei entrambe le forzature, mi servirebbe qualcosa per deregistrare solo prova2. In tutto ciò l’antenna wifi è rimasta sempre accesa e la modalità Hipri pure.

  6. Cosi’ ad occhio ti direi di provare a registrare di nuovo il secondo indirizzo sulla wifi invece che sulla hipri, magari funziona…

  7. Già provato, non funge…il requestRouteToHost funziona solo con l’hipri. Se x sbaglio trovi qualche soluzione alternativa fammi sapere, te ne sarei grato tantissimo!!!

Leave a Reply