Android Question Automatically saving email attachments through Intents and 'Complete action using...'

BenF

Member
Licensed User
Longtime User
Hi All,
I am trying to find a way to save email attachments into a specific folder, which I can then access from inside my app. This would be used to import a data (.txt) file into the app from an email, allowing datasets to be shared between users, without worrying about a live sync system. I have figured out what I believe to be half of the solution, using intents to capture the 'view' event upon trying to view the attachment. However, I cannot figure out how to go from that point to either saving the attachment directly or loading the content of the attachment into my app, so that I can write it to a .txt file.

I have added the following into the manifest editor:
B4X:
AddActivityText(ImportIntent,
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
</intent-filter>)

and this successfully displays my app in the 'Complete action using...' menu that appears when I try to view a .txt attachment. I have also added in the activity class, and have been able to call the starting intent, as below:
B4X:
Sub Globals
    Dim TextInput As Intent
End Sub

Sub Activity_Create(FirstTime As Boolean)
   
    TextInput = Activity.GetStartingIntent
   
End Sub

However, this is where I am getting stuck. How do I go from capturing the intent to saving the file? Do I need to use the uriString from the first intent to somehow call another, or is there something that I am missing? I have tried a few things, and trawled through the forums trying to find an answer, but haven't stumbled across anything as yet.

Any help on this would be greatly appreciated!

Cheers, Ben.
 

BenF

Member
Licensed User
Longtime User
Hi Erel,
the Mailparser is an option, I guess I could set it up to trigger from the ImportIntent Activity, using values that have been entered previously by the user through a form. However, I was hoping to be able to access the attachment directly (more akin to how I assume a document viewer like OfficeSuite would open it), rather than have a second call to parse the email and find the attachment. Is this likely to cause problems, as the email/attachment is effectively being opened from a third party app (eg the Gmail app)?
 
Upvote 0

BenF

Member
Licensed User
Longtime User
The Log entry above returns "no extras", without the quotes.
Should it be checked in Activity_Resume because it's more likely to catch it, given it will run after _Create and _Pause?
 
Upvote 0

BenF

Member
Licensed User
Longtime User
Hi Erel,
I have kept trying with this, and am still not able to get the file from the Action.View intent that is triggered from the Gmail app. I have found an example of what I am trying to do, but am unfamiliar with the language it is in/how I could convert it to b4a.
The example is here: http://stackoverflow.com/questions/17388756/how-to-access-gmail-attachment-data-in-my-app

The manifest code seems to match mine, with the exception of the mimetype. Theirs is set as "application/octet-stream", which failed to catch the Action.View intent when I inserted it into mine (I use text/plain instead, which does catch the intent). I have also added in the "Browsable" category to match theirs, although I don't really understand the significance... (it doesn't seem to have had any impact)

I can follow their onRestart() code for the following section:
B4X:
Intent  intent = getIntent();
InputStream
        is = null;
FileOutputStream
        os = null;
String  fullPath = null;

try {
    String  action = intent.getAction();

    if (!Intent.ACTION_VIEW.equals(action))
        return;

as this seems to be just defining variables and calling Activity.GetStartingIntent, and then confirming that the intent that has been captured is an Action.View. However, after this is where I am running into difficulties.

B4X:
    Uri    uri = intent.getData();
    String  scheme = uri.getScheme();
    String  name = null;

    if (scheme.equals("file")) {
        List<String>
            pathSegments = uri.getPathSegments();

        if (pathSegments.size() > 0)
            name = pathSegments.get(pathSegments.size() - 1);

    }

    else if (scheme.equals("content")) {
        Cursor  cursor = getContentResolver().query(
                uri,
                new String[]{MediaStore.MediaColumns.DISPLAY_NAME},
                null,
                null,
                null);

        cursor.moveToFirst();

        int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
        if (nameIndex >= 0)
            name = cursor.getString(nameIndex);

    }

    else
        return;

    if (name == null)
        return;

    int    n = name.lastIndexOf(".");
    String  fileName,
            fileExt;

    if (n == -1)
        return;

    else {
        fileName = name.substring(0, n);
        fileExt = name.substring(n);
        if (!fileExt.equals(".gcsb"))
            return;

    }

    fullPath = /*create full path to where the file is to go, including name/ext*/;

The code seems to be saving the Uri as a specific Uri object (which I haven't been able to find in B4A... Does it exist, or is there an equivalent? I think this may be the key, as everything after relies upon this reference to the file), and then using the Uri object to check the scheme (file or content). It then uses the scheme to determine how to build the destination filename; a file scheme loads the path segments into a list and gets the final segment, and a content scheme uses a ContentResolver (I assume this is some kind of string manipulator?) The next section then strips out the extension and ensures that the file is the correct format (.gcsb, in this case), and then specifies the Local destination.

I believe the scheme I am getting is Content, due to the Prefix of the Uri that is being returned from TextInput.GetData:
GetData = content://gmail-ls/[email protected]/messages/1123/attachments/0.1/BEST/false
although I haven't found any equivalent for uri.GetScheme, as scheme is listed as NOT CACHED in my intent (see attached image).
This means I will probably need to make use of something similar to the ContentResolver (if I understood what it is/how it is working).

The final section (apart from error trapping) then builds the new file (is it going character by character?), from the Uri object via an input stream to the file via an output stream.

B4X:
is = getContentResolver().openInputStream(uri);
    os = new FileOutputStream(fullPath);

    byte[]  buffer = new byte[4096];
    int    count;

    while ((count = is.read(buffer)) > 0)
        os.write(buffer, 0, count);

    os.close();
    is.close();
}

I believe I follow the logic of the coding, but I don't know if it is possible to translate it into B4A... any thoughts?

Cheers, and thanks for all your help so far.

Ben
 

Attachments

  • Intent Image.JPG
    Intent Image.JPG
    315 KB · Views: 200
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Uri object is available in the ContentResolver library.

Once you get it you can do something like:
B4X:
Sub HandleUri(u As Uri)
   Dim cr As JavaObject = GetContext.RunMethod("getContentResolver")
   Dim In As InputStream = cr.RunMethod("openInputStream", Array(u))
   Dim out As OutputStream = File.OpenOutput(...)
   File.Copy2(In, out)
   out.Close
End Sub

Sub GetContext As JavaObject
  Return GetBA.GetField("context")
End Sub

Sub GetBA As JavaObject
  Dim jo As JavaObject
  Dim cls As String = Me
  cls = cls.SubString("class ".Length)
  jo.InitializeStatic(cls)
  Return jo.GetFieldJO("processBA")
End Sub
 
Upvote 0

BenF

Member
Licensed User
Longtime User
Worked like a charm, thanks a million for that, I doubt I would have found my way through that on my own!!

The only thing left is the filename... Is there a way to pull the filename out of the Uri? If not tomorrow I'll build a selector which contains the options (there aren't too many formats that will be coming in)

Cheers again!

Ben
 
Upvote 0

BenF

Member
Licensed User
Longtime User
Unfortunately I don't think it is available. All of the options I have found for pulling filenames seem to rely on the file being locally stored. I think the easier option will be to build a header into each of my files, so that once I pass the text to a temporary "import.txt" file I can extract the correct filename from the first line of the .txt file. Something like this:
B4X:
Sub New_External_Import
    Import_List = File.ReadList(Import_Folder,"Import File.txt")
    Current_Import = Import_List.Get(0)
    Import_Filename = Current_Import & ".txt"
    Import_List.removeAt(0)
    If File.Exists(File.DirInternal,Import_Filename) = True Then
        Result = Msgbox2("Are you sure you want to overwrite the existing " & Current_Import & "?","Overwrite " & Current_Import & "?","Yes, Overwrite","","No, don't overwrite",Null)
        If Result = DialogResponse.POSITIVE Then
            File.Delete(File.DirInternal,Import_Filename)
            File.WriteList(File.DirInternal,Import_Filename,Import_List)
            Msgbox("Import Completed","")
        Else If Result = DialogResponse.NEGATIVE Then
            Msgbox("Import Cancelled","")
        End If
    Else If File.Exists(File.DirInternal,Import_Filename) = False Then
        File.WriteList(File.DirInternal,Import_Filename,Import_List)
        Msgbox("Import Completed","")
    End If
End Sub

Thanks Again!
 
Upvote 0

BenF

Member
Licensed User
Longtime User
It doesn't seem like it's available, the _data query returns Null... Can that code look at files that aren't locally stored? Is it the source of the file define what data is provided (ie different apps would have more or less details)?

It's no problem for me, the files I'm looking at are all built by the app, so it's easy enough to add in a header on each as it's created.
 
Upvote 0
Top