Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-136733

QDesktopServices is unable to open a file on Android

    XMLWordPrintable

Details

    • Bug
    • Resolution: Unresolved
    • Not Evaluated
    • None
    • 6.9.0
    • QPA: Android
    • None
    • Android

    Description

      I reported a similar bug on Qt 6.2.2 (https://bugreports.qt.io/browse/QTBUG-47979), was apparently fixed with Qt 6.4.1.

      Today, I'm upgrading to Qt 6.9.0, and it fails again. With a different error than the one reported for Qtbug 47979, that's why I prefer filling a new issue rather than reopening the old one.

      The code remains the same:

      main.cpp:

       

      #include <QApplication>
      #include "mainwindow.h"
      int main( int argc, char* argv[] )
      {
          QApplication app(argc, argv);
          MainFrame frame;
          frame.show();
          return app.exec();
      }
      

      mainwindow.h:

       

      #pragma once
      #include <QMainWindow>
      class MainFrame : public QMainWindow
      {
          Q_OBJECT
      public:
          MainFrame();
      public slots:
          void openFromQt();
          void openFromSDK();
      private:
          void fixOpenFile();
          std::string fileName;
      };
      

      mainwindow.cpp:

      #include "mainwindow.h"
      #include <QVBoxLayout>
      #include <QPushButton>
      #include <QFile>
      #include <QMessageBox>
      #include "qandroidextras_p.h"
      #define QtAndroid QtAndroidPrivate
      #define requestPermissionsSync requestPermissions
      #include <QJniObject>
      #include <QJniEnvironment>
      #include <QDesktopServices>
      #include <QUrl>
      #include <QDebug>
      #include <QStandardPaths>
      #include <string>
      #include <fstream>
      MainFrame::MainFrame()
      {
          // bug reported for Qt 6.2.2 here: https://bugreports.qt.io/browse/QTBUG-47979
          // workarounded by https://bugreports.qt.io/browse/QTBUG-67877
          // new bug reported for Qt 6.9.0
          fileName = (QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/" + CURRENT_MODULE_NAME + ".txt").toStdString();
          
          auto result1 = QtAndroid::requestPermission("android.permission.WRITE_EXTERNAL_STORAGE");
          result1.waitForFinished();
          if ( result1.result() != QtAndroid::PermissionResult::Authorized )
          {
              qCritical() << "Failed to get privileges for writting";
          }
          auto result2 = QtAndroid::requestPermission("android.permission.READ_EXTERNAL_STORAGE");
          result2.waitForFinished();
          if ( result2.result() != QtAndroid::PermissionResult::Authorized )
          {
              qCritical() << "Failed to get privileges for writting";
          }
          // create a file:
          std::fstream file;
          file.open( fileName.c_str(), std::ios_base::out );
          file << "Hello World!" << std::endl;
          file.close();
          if (QFile(fileName.c_str()).exists())
              QMessageBox::information(NULL,"","File exists");
          else
              QMessageBox::information(NULL,"","File does not exist");
          QWidget* centralWidget = new QWidget( this );
          centralWidget->setLayout( new QVBoxLayout() );
          QPushButton* qt_button = new QPushButton( "Open with Qt", centralWidget );
          QObject::connect( qt_button, SIGNAL(clicked()), this, SLOT(openFromQt()) );
          centralWidget->layout()->addWidget( qt_button );
          QPushButton* sdk_button = new QPushButton( "Open with SDK", centralWidget );
          QObject::connect( sdk_button, SIGNAL(clicked()), this, SLOT(openFromSDK()) );
          centralWidget->layout()->addWidget( sdk_button );
          setCentralWidget( centralWidget );
      }
      void MainFrame::fixOpenFile()
      {
          // see https://bugreports.qt.io/browse/QTBUG-67877
          QAndroidJniObject DisableDeathOnFileUriExposureObject("my/common/DisableDeathOnFileUriExposureHelper","()V");
          if ( DisableDeathOnFileUriExposureObject.isValid() )
          {
              jboolean worked = DisableDeathOnFileUriExposureObject.callMethod<jboolean>("doDisableDeathOnFileUriExposure","()Z");
              if ( worked )
                  qDebug() << "OK";
              else
                  qDebug() << "KO";
          }
          else
          {
              assert( false );
          }
      }
      void MainFrame::openFromQt()
      {
          fixOpenFile();
          // use Qt code to open a file. Works on PC, not on Android
          QUrl url = QUrl::fromLocalFile(fileName.c_str());
          QDesktopServices::openUrl(url);
      }
      std::string GetFileType( const std::string& fileName )
      {
          QAndroidJniObject javaFileName = QAndroidJniObject::fromString(fileName.c_str());    //type is valid
          QAndroidJniObject extension = QAndroidJniObject::callStaticObjectMethod("android/webkit/MimeTypeMap",
                                                                                  "getFileExtensionFromUrl",
                                                                                  "(Ljava/lang/String;)Ljava/lang/String;",
                                                                                  javaFileName.object<jobject>());
          if ( extension.isValid() )
          {
              std::string ext = extension.toString().toStdString();
              QAndroidJniObject mime = QAndroidJniObject::callStaticObjectMethod("android/webkit/MimeTypeMap",
                                                                                 "getSingleton",
                                                                                 "()Landroid/webkit/MimeTypeMap;");
              if ( mime.isValid() )
              {
                  QAndroidJniObject type = mime.callObjectMethod("getMimeTypeFromExtension",
                                                                 "(Ljava/lang/String;)Ljava/lang/String;",
                                                                 extension.object<jobject>() );
                  if ( type.isValid() )
                  {
                      return type.toString().toSDEString();
                  }
              }
          }
          return "text/plain";
      }
      bool startIntentToOpenFile( QAndroidJniObject& activity, const std::string& fileName, bool separatApp )
      {
          bool opened = false;
          if ( activity.isValid() )
          {
              QAndroidJniObject intent("android/content/Intent","()V");
              if ( intent.isValid() )
              {
                  QAndroidJniObject name = QAndroidJniObject::fromString(fileName.c_str());    //type is valid
                  std::string mimeType = GetFileType(fileName);
                  QAndroidJniObject type = QAndroidJniObject::fromString(mimeType.c_str());    //type is valid
                  // should we actually use QAndroidJniObject::getStaticObjectField<jstring>("android/intent/action","ACTION_VIEW"); ?
                  QAndroidJniObject action = QAndroidJniObject::fromString("android.intent.action.VIEW");
                  if ( type.isValid() && name.isValid() && action.isValid() )
                  {
                      QAndroidJniObject file( "java/io/File","(Ljava/lang/String;)V",name.object<jobject>());
                      if ( file.isValid() )
                      {
                          jboolean exists = file.callMethod<jboolean>("exists","()Z");
                          if ( exists )
                          {
                              QAndroidJniObject uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "fromFile", "(Ljava/io/File;)Landroid/net/Uri;", file.object<jobject>());
                              if ( uri.isValid() )
                              {
                                  //QAndroidJniObject string = uri.callObjectMethod("toString","()Ljava/lang/String;");
                                  //const char *str1 = string.toString().toStdString().c_str();
                                  intent.callObjectMethod("setDataAndType","(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/Intent;",uri.object<jobject>(),type.object<jobject>());
                                  intent.callObjectMethod("setAction","(Ljava/lang/String;)Landroid/content/Intent;",action.object<jobject>());
                                  if ( separatApp )
                                      intent.callObjectMethod("setFlags","(I)Landroid/content/Intent;",0x10000000);
                                  if ( intent.isValid() )
                                  {
                                      //activity.callObjectMethod("startActivity","(Landroid/content/Intent;)V",intent.object<jobject>());
                                      // recommended by https://bugreports.qt-project.org/browse/QTBUG-41395
                                      activity.callMethod<void>("startActivity","(Landroid/content/Intent;)V",intent.object<jobject>());
                                      opened = true;
                                  }
                              }
                          }
                      }
                  }
              }
          }
          return opened;
      }
      void MainFrame::openFromSDK()
      {
          fixOpenFile();
          auto activity = QJniObject(QNativeInterface::QAndroidApplication::context());
          startIntentToOpenFile( activity, fileName, false );
      }
      

       

      DisableDeathOnFileUriExposureHelper.java:

       

       

      package my.common;
      import java.lang.reflect.*;
      import android.os.StrictMode;
      public class DisableDeathOnFileUriExposureHelper
      {
          public boolean doDisableDeathOnFileUriExposure()
          {
              try{
                 Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
                 m.invoke(null);
                 return true;
              }catch(Exception e){
                 e.printStackTrace();
                 return false;
              }
          }
      }
      

      `fixOpenFile` and are here to workaround https://bugreports.qt.io/browse/QTBUG-67877

       

      In the end:

      • Opening the file with the SDK (MainFrame::openFromSDK) works. This means trhe file exists for good and we have permission to access it
      • Opening the file with QDesktopServices (MainFrame::openFromQt()) fails, even if `DisableDeathOnFileUriExposureHelper` was called.

      The error reported is a new one:

      W/default (26178): java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Documents/qtbug_androidopenfile.txt
      W/default (26178):     at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:867)
      W/default (26178):     at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:467)
      W/default (26178):     at org.qtproject.qt.android.QtNative.startQtApplication(Native Method)
      W/default (26178):     at org.qtproject.qt.android.QtNative$$ExternalSyntheticLambda4.run(D8$$SyntheticClass:0)
      W/default (26178):     at org.qtproject.qt.android.QtThread$1.run(QtThread.java:25)
      W/default (26178):     at java.lang.Thread.run(Thread.java:764)
      W/default (26178): 

       

      Attachments

        Issue Links

          No reviews matched the request. Check your Options in the drop-down menu of this sections header.

          Activity

            People

              qtandroidteam Qt Android Team
              jpo38 Jean Porcherot
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

                Created:
                Updated:

                Gerrit Reviews

                  There are no open Gerrit changes