4

I'm writing an Android FMX app in Delphi 10.3 Rio. There I'm selecting photos from the gallery (via TJIntent.JavaClass.ACTION_OPEN_DOCUMENT) and getting back Jnet_Uri entries. I can use those to read image EXIF (with TJExifInterface). Now I also need to load these images into a stream for further processing. How do I do this?

When I try to convert Jnet_Uri to a path with uri.getPath, it comes out like /document/image:26591. uri.toString gives me content://com.android.providers.media.documents/document/image%3A26674. TMemoryStream.LoadFromFile fails to load from both of these paths:

Cannot open file "/document/image:26724". No such file or directory
Cannot open file "/content:/com.android.providers.media.documents/document/image%3A26724". Not a directory

Hence the question, how knowing a Jnet_Uri do I load files contents into a stream?

Kromster
  • 7,181
  • 7
  • 63
  • 111

2 Answers2

4

I was able to read the data via JInputStream:

var
  uri: Jnet_Uri;
  ms: TMemoryStream;
  jis: JInputStream;
  b: TJavaArray<Byte>;
begin
  uri := .. some uri, alike "/document/image:26591"

  ms := TMemoryStream.Create;

  // Need to read via JInputStream, since Uri is not a file
  jis := TAndroidHelper.Context.getContentResolver.openInputStream(uri);
  b := TJavaArray<Byte>.Create(jis.available);
  jis.read(b);
  ms.Write(b.Data^, b.Length);
  jis.close;

   .. do something with Stream now
Kromster
  • 7,181
  • 7
  • 63
  • 111
0

I'm using this code to access files on Android via content uri as TStream:

stream := TipJavaContentResolverStream.Create(uri);

Tested on Delphi 11, TipJavaContentResolverStream work with files openned as ParcelFileDescriptor.AutoCloseInputStream, i don't know TJIntent.JavaClass.ACTION_OPEN_DOCUMENT use this model or not, try it:

{
   The module contains a class for retrieving data from ContentResolver as TStream
   Author: Victor Fedorenkov
}
unit ipJavaContentResolverStreamUnit.Android;

interface

uses
  SysUtils, Types,

  Androidapi.JNI.Net,
  Androidapi.Jni.Os,
  Androidapi.Helpers,
  Androidapi.JNIBridge,
  Androidapi.JNI.JavaTypes,

  ipJavaFileInputStreamUnit.Android;

type
  TipJavaContentResolverStream = class(TipJavaFileInputStream)
  private
    FContentUri: string;
  public
    constructor Create(const AContentUri: string);
  end;

implementation

const
  JParcelFileDescriptor_AutoCloseInputStream_ClassName = 'android.os.ParcelFileDescriptor$AutoCloseInputStream';

{ TipJavaContentResolverStream }

constructor TipJavaContentResolverStream.Create(const AContentUri: string);
var
  JUri: Jnet_Uri;
  JStream: JInputStream;
  ContentStreamClassName: string;
begin
  FContentUri := AContentUri;

  JUri := TJnet_Uri.JavaClass.parse(StringToJString(AContentUri));

  JStream := TAndroidHelper.Context.getContentResolver.openInputStream(JUri);

  if not Assigned(JStream) then
    raise Exception.Create('Unable get content stream: ' + AContentUri);

  ContentStreamClassName := JStringToString(JStream.getClass.getName);

  if ContentStreamClassName <> JParcelFileDescriptor_AutoCloseInputStream_ClassName then
    raise Exception.CreateFmt('Can''t open content class %s from %s', [
      ContentStreamClassName, AContentUri]);

  inherited Create(TJParcelFileDescriptor_AutoCloseInputStream.Wrap((JStream as ILocalObject).GetObjectID));
end;

end.
{
  Adapter for reading JFileInputStream as a regular TStream
  Author: Victor Fedorenkov
}

unit ipJavaFileInputStreamUnit.Android;

interface

uses
  SysUtils, Classes, Math, RTLConsts,

  Androidapi.Jni,
  Androidapi.JNIBridge,
  Androidapi.JNI.JavaTypes;

type
  TipJavaFileInputStream = class(TStream)
  private
    FJStream: JFileInputStream;
  protected
      function GetSize: Int64; override;
  public
    constructor Create(AJStream: JFileInputStream);

    function Read(var Buffer; Count: LongInt): LongInt; override;
    function Write(const Buffer; Count: LongInt): LongInt; override;
    function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;

    property JStream: JFileInputStream read FJStream;
  end;

implementation

{ TipJavaFileInputStream }

constructor TipJavaFileInputStream.Create(AJStream: JFileInputStream);
begin
  FJStream := AJStream;
end;

function TipJavaFileInputStream.GetSize: Int64;
begin
  Result := FJStream.getChannel.size;
end;

function TipJavaFileInputStream.Write(const Buffer; Count: LongInt): LongInt;
begin
  raise EStreamError.CreateRes(@SWriteError);
end;

function TipJavaFileInputStream.Read(var Buffer; Count: LongInt): LongInt;
var
  CanRead: LongInt;
  ReadRes: Integer;
  b: TJavaArray<Byte>;
begin
  Result := 0;

  b := nil;
  try
    repeat
      // Calculate how much we can read from the current position
      CanRead := Min(FJStream.available, Count - Result);

      // Allocate a buffer for reading if it doesn't exist or is not large enough
      if (not Assigned(b)) or (b.Length < CanRead) then
      begin
        FreeAndNil(b);
        b := TJavaArray<Byte>.Create(CanRead);
      end;

      // Read the data
      ReadRes := FJStream.read(b, 0, CanRead);

      // Copy what was successfully read into the buffer
      if ReadRes > 0 then
      begin
        Move(b.Data^, (PByte(@Buffer) + Result)^, ReadRes);
        Inc(Result, ReadRes);
      end;

      // Continue reading until there is nothing left to read
    until ReadRes <= 0;

  finally
    FreeAndNil(b);
  end;
end;

function TipJavaFileInputStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
var
  NewPosition: Int64;
begin
  // To avoid a warning
  NewPosition := 0;

  case Origin of
    soBeginning: NewPosition := Offset;
    soCurrent: NewPosition := FJStream.getChannel.position + Offset;
    soEnd: NewPosition := Size + Offset;
  end;

  Result := FJStream.getChannel.position(NewPosition).position;
end;

end.