Search code examples
androidqtqt5qeventqapplication

How to register for ACTION_VIEW Intent on Android? Why doesn't my QApplication receive QEvent::FileOpen events?


I am trying to register a QtQuick Android application to open a certain class of files and handle them.

From what I gather, when a file is opened with a QApplication it results in a QEvent::FileOpen being fired.

The strongest (if inconclusive) evidence for this I have is this commit found in a production system, plus a number of blog posts and Google results.

So, I first create a new empty QtQuick project.

I then write an EventFilter, like this:

#include <QtGui>
#include <QApplication>

#include <QFileOpenEvent>

class MyEventFilter : public QObject {
  public:
  MyEventFilter();
  virtual ~MyEventFilter();
protected:
  virtual bool eventFilter(QObject* object,QEvent* event);
};

#include "myeventfilter.h"

MyEventFilter::MyEventFilter() : QObject () {}
MyEventFilter::~MyEventFilter() {}
bool MyEventFilter::eventFilter(QObject* object,QEvent* e) {
  qDebug() << "Received" << e->type();
  if (e->type() == QEvent::FileOpen) {
      QFile file(static_cast<QFileOpenEvent *>(e)->file());
      qDebug() << "File to open:" << file.fileName();
      // This is where I would do stuff
      return true;
  } else {
      return QObject::eventFilter(object,e);
  }
}

I then register it and edit my manifest accordingly, by adding

        <intent-filter android:label="Foo File" android:priority="1">
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
            <data android:scheme="file" android:host="success"/>
            <data android:host="*"/>
            <data android:mimeType="*/*"/>
            <data android:pathPattern=".*\\.foo"/>
        </intent-filter>

At this point, I launch my application in the Android emulator.

When I open a .foo file from ES File Explorer in my AVD with Nexus 5, API 22, my application is indeed brought in the foreground, but no FileOpen events are logged.

If I set up a breakpoint inside my filter method I don't seem to be hitting it.

This puzzles me.

I tried the alternate approach by extending QApplication and overloading event(QEvent *) with pretty much the same results (no hits).

What could I be doing wrong?


Please find the complete repository for this MWE here.


Solution

  • I'm gonna answer my own question after perusing Bogdan's tutorials and receiving some invaluable help from #qt on Freenode.

    It looks like opening a file in Android does not, in fact, result in a QEvent::FileOpen, which is useful only on systems with a window manager.

    Instead, one has to subclass QtActivity and override onIntent in Java, then call an appropriate C++ method with JNI:

    package com.foo;
    
    import android.content.Intent;
    
    import org.qtproject.qt5.android.bindings.QtActivity;
    
    class Bar
    {
        public static native void openUri(String uri);
        // Notice the 'native' keyword
    }
    
    public class MyActivity extends QtActivity {
        public void onNewIntent(Intent i) {
            if (i.getAction() == Intent.ACTION_VIEW) {
                Bar.openUri(i.getData().toString());
            }
            super.onNewIntent(i);
        }
    }
    

    class URIHandler : public QObject {
    public:
        void openUri(QString uri) {
            qDebug() << "Open URI" << uri;
        }
    }
    

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    JNIEXPORT void JNICALL
      Java_com_foo_Bar_openUri(JNIEnv *env,
                                                        jobject obj,
                                                        jstring uri)
    {
        jboolean isCopy;
        isCopy = false;
        const char* utf = env->GetStringUTFChars(uri, &isCopy);
        handler.openUri(QString(utf));
        env->ReleaseStringUTFChars(uri, utf);
    
    }
    
    #ifdef __cplusplus
    }
    #endif
    

    Note that there are some tricky subtleties with JNI method names.

    You can find the updated, working version of this example here.