androiddelphiindy10

How to make TIdStackLocalAddress work on Android?


Modeled on @Remy Lebeau's very helpful code example in this SO item, I have the below function, which works as expected in Windows 10 to return; e.g.:

  No of Addresses: 4
  IPv4 Addresses:
  IP Address #0: 192.168.56.1 - 255.255.255.0 - 11
  IP Address #1: 192.168.1.7 - 255.255.255.0 - 8
  IP: 192.168.56.1

But when I change the platform to Android 64-bit and run it on my Samsung S21 while WiFi is enabled and connected to my LAN, it returns only the local loopback IP and none of the other values; e.g.:

No of Addresses: 1
IPv4 Addresses:
IP Address #0: 127.0.0.1 -  - 0
IP: 127.0.0.1

I had hoped that this had to do with lack of some permission, but as Remy points out in a comment, the problem is that the Indy10 method is broken for Android and one needs to use Dave Nottage's workaround below. If you want also to get the NetMask that the Indy method is supposed to return, you can use my solution below posted as an answer.

function getLocalIP: string;
begin
  Result := '';
  try
    var IPList := TIdStackLocalAddressList.Create;
    try
      TIdStack.IncUsage;
      try
        GStack.GetLocalAddressList(IPList);
      finally
        TIdStack.DecUsage;
      end;

      WriteLog('DEBUG', 'No of Addresses: ' + IntToStr(IPList.Count));
      WriteLog('DEBUG', 'IPv4 Addresses:');

      var IPStrings := TStringList.Create;
      try
        for var i in IPList do
        begin
          if TIdStackLocalAddressIPv4(i).IPVersion = Id_IPv4 then
          begin
            IPStrings.Add(TIdStackLocalAddressIPv4(i).IPAddress + ' - ' + TIdStackLocalAddressIPv4(i).SubNetMask
              + ' - ' + TIdStackLocalAddressIPv4(i).InterfaceIndex.ToString);
          end;
        end;

        // show IP Addresses in the log file
        for var i := 0 to IPStrings.Count-1 do
          WriteLog('DEBUG', 'IP Address #' + IntToStr(i) + ': ' + IPStrings[i]);
        Result := IPStrings[0].Split([' - '])[0];
        WriteLog('DEBUG', 'IP: ' + Result);
      finally
        IPStrings.Free;
      end;
    finally
      IPList.Free;
    end;
  except
    On E: Exception do
    begin
      Result := '';
      WriteLog('ERROR', 'IP Error: ' + E.message);
    end;
  end;
end;

Solution

  • Here is what I came up with, based on @Dave Nottage's gist, to retrieve Android IPv4 addresses and subnet masks:

    unit AndroidGetInetAddress;
    
    interface
    
    {$IFDEF Android}
    uses
      System.SysUtils, {System.Types, System.UITypes, System.Classes, System.Variants,}
      IdStack{, IdBaseComponent, IdComponent, IdUDPBase, IdUDPClient, IdGlobal};
    
    procedure GetLocalAddressList(const AAddresses: TIdStackLocalAddressList);
    {$ENDIF}
    
    implementation
    
    {$IFDEF Android}
    
    uses
      Androidapi.JNI.Java.Net, Androidapi.JNI.JavaTypes, Androidapi.Helpers,  Androidapi.JNIBridge;
    
    { Dumb conversion of number of bits to 4-byte SubnetMask string}
    function SubnetMask(ACode: string): string;
    begin
      var NCode := ACode.ToInteger;
      if NCode < 25 then
        Result := '255.255.255.' + (%11111111 shr NCode).ToString
      else if NCode < 33 then
        Result := '255.255.' + (%11111111 shr NCode-24).ToString + '.0'
      else if NCode < 41 then
        Result := '255.' + (%11111111 shr NCode-32).ToString + '.0.0'
      else if NCode < 49 then
        Result := (%11111111 shr NCode-40).ToString + '.0.0.0'
      else
        Result := '0.0.0.0.';
    end;
    
    procedure GetLocalAddressList(const AAddresses: TIdStackLocalAddressList);
    
    begin
      AAddresses.Clear;
      var LInterfaces := TJNetworkInterface.JavaClass.getNetworkInterfaces;
    
      while LInterfaces.hasMoreElements do
      begin
        var LInterface := TJNetworkInterface.Wrap(TAndroidHelper.JObjectToID(LInterfaces.nextElement));
    
    ////// method getInetAddresses evidently does not return a subnet mask
    //    var LAddresses := LInterface.getInetAddresses;
    //    while LAddresses.hasMoreElements do
    //    begin
    //      var LAddress := TJInetAddress.Wrap(TAndroidHelper.JObjectToID(LAddresses.nextElement));
    //      WriteLog('DEBUG', 'getAddress: ' + JStringToString(LAddress.ToString));
    //      if LAddress.isLoopbackAddress then
    //        Continue;
    //      // Dave Nottage: Hack until I can find out how to check properly
    //      WriteLog('DEBUG', 'LName: ' + JStringToString(LAddress.getClass.getName));
    //      WriteLog('DEBUG', 'LHostAddress: ' + JStringToString(LAddress.getHostAddress));
    //    end;
    
    ////// method getInterfaceAddresses does return IP address, mask, and '[null]' or broadcast IP
        var LInterfaceAddresses := LInterface.getInterfaceAddresses;
        for var i := 0 to LInterfaceAddresses.size-1 do
        begin
          var LHostAddress := JStringToString(LInterfaceAddresses.get(i).toString);
    //      WriteLog('DEBUG', 'Address ' + i.ToString + ': ' + LHostAddress);
          if not LHostAddress.Contains('[null]') then
          // Have IPv4Inet address with subnet mask and broadcast address
          begin
            var Address := LHostAddress.Split(['/','[',']',' '], TStringSplitOptions.ExcludeEmpty);
            TIdStackLocalAddressIPv4.Create(AAddresses, Address[0], SubnetMask(Address[1]))
          end;
        end;
      end;
    end;
    {$ENDIF}
    
    end.
    

    "Use at your own risk" :)