• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MerginMaps / input / 6422038473

05 Oct 2023 04:51PM UTC coverage: 62.265% (+0.4%) from 61.9%
6422038473

push

github

web-flow
Remove subscription pages for iOS and call-to-actions (#2835)

* remove subscription pages for ios and call-to-actions

* bring back button to account page to get to the dashboard

* merge url params

7681 of 12336 relevant lines covered (62.26%)

105.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.25
/app/androidutils.cpp
1
/***************************************************************************
2
 *                                                                         *
3
 *   This program is free software; you can redistribute it and/or modify  *
4
 *   it under the terms of the GNU General Public License as published by  *
5
 *   the Free Software Foundation; either version 2 of the License, or     *
6
 *   (at your option) any later version.                                   *
7
 *                                                                         *
8
 ***************************************************************************/
9

10
#include "androidutils.h"
11

12
#ifdef ANDROID
13
#include <QtCore/private/qandroidextras_p.h>
14
#include <QCoreApplication>
15
#include <QJniObject>
16
#include <QJniEnvironment>
17
#include <QDebug>
18
#include <QFileInfo>
19
#include <QDir>
20
#include <QStandardPaths>
21

22
#include "coreutils.h"
23
#endif
24

25
AndroidUtils::AndroidUtils( QObject *parent ): QObject( parent )
18✔
26
{
27
}
18✔
28

29
void AndroidUtils::showToast( QString message )
×
30
{
31
#ifdef ANDROID
32
  QNativeInterface::QAndroidApplication::runOnAndroidMainThread( [message]()
33
  {
34
    QJniObject toast = QJniObject::callStaticObjectMethod(
35
                         "android.widget.Toast",
36
                         "makeText",
37
                         "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
38
                         QNativeInterface::QAndroidApplication::context(),
39
                         QJniObject::fromString( message ).object(),
40
                         jint( 1 ) );
41
    toast.callMethod<void>( "show" );
42
  } );
43
#else
44
  Q_UNUSED( message )
45
#endif
46
}
×
47

48
bool AndroidUtils::isAndroid() const
×
49
{
50
#ifdef ANDROID
51
  return true;
52
#else
53
  return false;
×
54
#endif
55
}
56

57
bool AndroidUtils::checkAndAcquirePermissions( const QString &permissionString )
×
58
{
59
#ifdef ANDROID
60
  auto r = QtAndroidPrivate::checkPermission( permissionString ).result();
61
  if ( r == QtAndroidPrivate::Denied )
62
  {
63
    r = QtAndroidPrivate::requestPermission( permissionString ).result();
64
    if ( r == QtAndroidPrivate::Denied )
65
    {
66
      return false;
67
    }
68
  }
69
#else
70
  Q_UNUSED( permissionString )
71
#endif
72
  return true;
×
73
}
74

75
QString AndroidUtils::externalStorageAppFolder()
×
76
{
77
#ifdef ANDROID
78
  // AppDataLocation returns two paths, first is internal app storage and the second is external storage
79
  QStringList paths = QStandardPaths::standardLocations( QStandardPaths::AppDataLocation );
80
  if ( paths.size() > 1 )
81
  {
82
    return paths.at( 1 );
83
  }
84
  else
85
  {
86
    CoreUtils::log( "StorageException", "Path from QStandardPaths do not include external storage!! Using path: " + paths.at( 0 ) );
87
    return paths.at( 0 );
88
  }
89
#endif
90

91
  return QString();
×
92
}
93

94
QString AndroidUtils::readExif( const QString &filePath, const QString &tag )
×
95
{
96
#ifdef ANDROID
97
  QJniObject jFilePath = QJniObject::fromString( filePath );
98
  QJniObject jTag = QJniObject::fromString( tag );
99
  QJniObject attribute = QJniObject::callStaticObjectMethod( "uk.co.lutraconsulting.EXIFUtils",
100
                         "getEXIFAttribute",
101
                         "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
102
                         jFilePath.object<jstring>(),
103
                         jTag.object<jstring>() );
104
  return attribute.toString();
105
#else
106
  Q_UNUSED( filePath )
107
  Q_UNUSED( tag )
108
  return QString();
×
109
#endif
110
}
111

112
void AndroidUtils::turnBluetoothOn()
×
113
{
114
#ifdef ANDROID
115
  if ( !isBluetoothTurnedOn() )
116
  {
117
    QJniObject ACTION_BT = QJniObject::getStaticObjectField(
118
                             "android/bluetooth/BluetoothAdapter",
119
                             "ACTION_REQUEST_ENABLE",
120
                             "Ljava/lang/String;"
121
                           );
122

123
    QJniObject intent(
124
      "android/content/Intent",
125
      "(Ljava/lang/String;)V",
126
      ACTION_BT.object()
127
    );
128

129
    if ( ACTION_BT.isValid() && intent.isValid() )
130
    {
131
      QtAndroidPrivate::startActivity( intent.object<jobject>(), BLUETOOTH_CODE, this );
132
    }
133
  }
134
#endif
135
}
×
136

137
bool AndroidUtils::isBluetoothTurnedOn()
×
138
{
139
#ifdef ANDROID
140
  return mBluetooth.hostMode() != QBluetoothLocalDevice::HostPoweredOff;
141
#else
142
  return true;
×
143
#endif
144
}
145

146
void AndroidUtils::quitApp()
×
147
{
148
#ifdef ANDROID
149
  auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
150
  activity.callMethod<void>( "quitGracefully", "()V" );
151

152
  // If quitGracefully failed or this device is not of specified manufacturer, let's exit via QT
153
  QCoreApplication::quit();
154
#endif
155
}
×
156

157
bool AndroidUtils::requestStoragePermission()
×
158
{
159
#ifdef ANDROID
160
  double buildVersion = QSysInfo::productVersion().toDouble();
161

162
  //
163
  // Android SDK 33 has a new set of permissions when reading external storage.
164
  // See https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE
165
  //
166
  QString storagePermissionType = QStringLiteral( "android.permission.READ_MEDIA_IMAGES" );
167
  if ( buildVersion < ANDROID_VERSION_13 )
168
  {
169
    storagePermissionType = QStringLiteral( "android.permission.READ_EXTERNAL_STORAGE" );
170
  }
171

172
  if ( !checkAndAcquirePermissions( storagePermissionType ) )
173
  {
174
    auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
175
    jboolean res = activity.callMethod<jboolean>( "shouldShowRequestPermissionRationale", "(Ljava/lang/String;)Z", QJniObject::fromString( "android.permission.WRITE_EXTERNAL_STORAGE" ).object() );
176
    if ( !res )
177
    {
178
      // permanently denied permission, user needs to go to settings to allow permission
179
      showToast( tr( "Storage permission is permanently denied, please allow it in settings in order to load pictures from gallery" ) );
180
    }
181
    else
182
    {
183
      showToast( tr( "Input needs a storage permission in order to load pictures from gallery" ) );
184
    }
185
    return false;
186
  }
187
#endif
188
  return true;
×
189
}
190

191
bool AndroidUtils::requestCameraPermission()
×
192
{
193
#ifdef ANDROID
194
  if ( checkAndAcquirePermissions( "android.permission.CAMERA" ) == false )
195
  {
196
    auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
197
    jboolean res = activity.callMethod<jboolean>( "shouldShowRequestPermissionRationale", "(Ljava/lang/String;)Z", QJniObject::fromString( "android.permission.CAMERA" ).object() );
198
    if ( !res )
199
    {
200
      // permanently denied permission, user needs to go to settings to allow permission
201
      showToast( tr( "Camera permission is permanently denied, please allow it in settings" ) );
202
    }
203
    else
204
    {
205
      showToast( tr( "We need a camera permission in order to take a photo" ) );
206
    }
207
    return false;
208
  }
209
#endif
210
  return true;
×
211
}
212

213
bool AndroidUtils::requestMediaLocationPermission()
×
214
{
215
#ifdef ANDROID
216
  // ACCESS_MEDIA_LOCATION is a runtime permission without UI dialog (User do not need to click anything to grant it, it is granted automatically)
217
  return checkAndAcquirePermissions( "android.permission.ACCESS_MEDIA_LOCATION" );
218
#endif
219
  return true;
×
220
}
221

222
void AndroidUtils::callImagePicker()
×
223
{
224
#ifdef ANDROID
225

226
  if ( !requestStoragePermission() )
227
  {
228
    return;
229
  }
230

231
  // request media location permission to be able to read EXIF metadata from gallery image
232
  // it is not a mandatory permission, so continue even if it is rejected
233
  requestMediaLocationPermission();
234

235
  QJniObject ACTION_PICK = QJniObject::getStaticObjectField( "android/content/Intent", "ACTION_PICK", "Ljava/lang/String;" );
236
  QJniObject EXTERNAL_CONTENT_URI = QJniObject::getStaticObjectField( "android/provider/MediaStore$Images$Media", "EXTERNAL_CONTENT_URI", "Landroid/net/Uri;" );
237

238
  QJniObject intent = QJniObject( "android/content/Intent", "(Ljava/lang/String;Landroid/net/Uri;)V", ACTION_PICK.object<jstring>(), EXTERNAL_CONTENT_URI.object<jobject>() );
239

240
  if ( ACTION_PICK.isValid() && intent.isValid() )
241
  {
242
    intent.callObjectMethod( "setType", "(Ljava/lang/String;)Landroid/content/Intent;", QJniObject::fromString( "image/*" ).object<jstring>() );
243
    QtAndroidPrivate::startActivity( intent.object<jobject>(), MEDIA_CODE, this ); // this as receiver
244
  }
245
#endif
246
}
×
247

248
void AndroidUtils::installQRCodeScanner()
×
249
{
250
#ifdef ANDROID
251

252
  QJniObject activity = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.InstallScannerActivity" ) );
253
  QJniObject intent = QJniObject( "android/content/Intent", "(Ljava/lang/String;)V", activity.object<jstring>() );
254

255
  QJniObject packageName = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting" ) );
256
  QJniObject className = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.InstallScannerActivity" ) );
257

258
  intent.callObjectMethod( "setClassName", "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;", packageName.object<jstring>(), className.object<jstring>() );
259

260
  QtAndroidPrivate::startActivity( intent, INSTALL_QR_SCANNER_CODE, this );
261
#endif
262
}
×
263

264
void AndroidUtils::scanQRCode()
×
265
{
266
#ifdef ANDROID
267

268
  QJniObject activity = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.ScannerActivity" ) );
269
  QJniObject intent = QJniObject( "android/content/Intent", "(Ljava/lang/String;)V", activity.object<jstring>() );
270

271
  QJniObject packageName = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting" ) );
272
  QJniObject className = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.ScannerActivity" ) );
273

274
  intent.callObjectMethod( "setClassName", "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;", packageName.object<jstring>(), className.object<jstring>() );
275

276
  QtAndroidPrivate::startActivity( intent, QR_SCAN_CODE, this );
277
#endif
278
}
×
279

280
void AndroidUtils::callCamera( const QString &targetPath )
×
281
{
282
#ifdef ANDROID
283
  if ( !requestCameraPermission() )
284
  {
285
    return;
286
  }
287

288
  // request media location permission to be able to read EXIF metadata from captured image
289
  // it is not a mandatory permission, so continue even if it is rejected
290
  requestMediaLocationPermission();
291

292
  const QString IMAGE_CAPTURE_ACTION = QString( "android.media.action.IMAGE_CAPTURE" );
293

294
  QJniObject activity = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.CameraActivity" ) );
295
  QJniObject intent = QJniObject( "android/content/Intent", "(Ljava/lang/String;)V", activity.object<jstring>() );
296

297
  QJniObject packageName = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting" ) );
298
  QJniObject className = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.CameraActivity" ) );
299

300
  intent.callObjectMethod( "setClassName", "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;", packageName.object<jstring>(), className.object<jstring>() );
301

302
  QJniObject extra = QJniObject::fromString( "targetPath" );
303
  QJniObject my_prefix = QJniObject::fromString( targetPath );
304

305
  intent.callObjectMethod( "putExtra",
306
                           "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
307
                           extra.object<jstring>(),
308
                           my_prefix.object<jstring>() );
309

310
  if ( intent.isValid() )
311
  {
312
    QtAndroidPrivate::startActivity( intent.object<jobject>(), CAMERA_CODE, this );
313
  }
314
#else
315
  Q_UNUSED( targetPath )
316
#endif
317
}
×
318

319
#ifdef ANDROID
320
void AndroidUtils::handleActivityResult( int receiverRequestCode, int resultCode, const QJniObject &data )
321
{
322
  jint RESULT_OK = QJniObject::getStaticField<jint>( "android/app/Activity", "RESULT_OK" );
323
  jint RESULT_CANCELED = QJniObject::getStaticField<jint>( "android/app/Activity", "RESULT_CANCELED" );
324

325
  if ( receiverRequestCode == BLUETOOTH_CODE )
326
  {
327
    if ( resultCode == RESULT_OK )
328
    {
329
      emit bluetoothEnabled( true );
330
    }
331
    else
332
    {
333
      emit bluetoothEnabled( false );
334
    }
335

336
    return;
337
  }
338

339
  if ( receiverRequestCode == QR_SCAN_CODE )
340
  {
341
    const QJniObject key = QJniObject::fromString( "message" );
342
    const QJniObject rawResponse = data.callObjectMethod( "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;", key.object() );
343

344
    QString response;
345

346
    if ( rawResponse.isValid() )
347
    {
348
      response = rawResponse.toString();
349
    }
350

351
    CoreUtils::log( QStringLiteral( "AndroidUtils" ), QStringLiteral( "Scan activity finished, raw value: %1, success: %2" ).arg( response ).arg( resultCode ) );
352

353
    if ( resultCode == RESULT_OK )
354
    {
355
      emit qrScanFinished( response );
356
    }
357
    else
358
    {
359
      if ( response == "not_installed" )
360
      {
361
        emit qrScannerMissing();
362
      }
363
      else
364
      {
365
        emit qrScanAborted();
366
      }
367
    }
368

369
    return;
370
  }
371

372
  if ( receiverRequestCode == INSTALL_QR_SCANNER_CODE )
373
  {
374
    return;
375
  }
376

377
  if ( resultCode == RESULT_CANCELED )
378
  {
379
    QJniObject RESULT_STRING = QJniObject::fromString( QStringLiteral( "__RESULT__" ) );
380
    // User has triggered cancel, result has no data.
381
    if ( !data.isValid() )
382
    {
383
      return;
384
    }
385

386
    QJniObject errorJNI = data.callObjectMethod( "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;", RESULT_STRING.object<jstring>() );
387
    // Internal cancelation due to an error
388
    QString errorMsg = errorJNI.toString();
389
    showToast( errorMsg );
390
    return;
391
  }
392

393
  if ( receiverRequestCode == MEDIA_CODE && resultCode == RESULT_OK )
394
  {
395
    QJniObject uri = data.callObjectMethod( "getData", "()Landroid/net/Uri;" );
396
    QJniObject mediaStore = QJniObject::getStaticObjectField( "android/provider/MediaStore$MediaColumns", "DATA", "Ljava/lang/String;" );
397
    QJniEnvironment env;
398
    jobjectArray projection = ( jobjectArray )env->NewObjectArray( 1, env->FindClass( "java/lang/String" ), NULL );
399
    jobject projectionDataAndroid = env->NewStringUTF( mediaStore.toString().toStdString().c_str() );
400
    env->SetObjectArrayElement( projection, 0, projectionDataAndroid );
401
    auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
402
    QJniObject contentResolver = activity.callObjectMethod( "getContentResolver", "()Landroid/content/ContentResolver;" );
403
    QJniObject cursor = contentResolver.callObjectMethod( "query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;", uri.object<jobject>(), projection, NULL, NULL, NULL );
404
    jint columnIndex = cursor.callMethod<jint>( "getColumnIndex", "(Ljava/lang/String;)I", mediaStore.object<jstring>() );
405
    cursor.callMethod<jboolean>( "moveToFirst", "()Z" );
406
    QJniObject result = cursor.callObjectMethod( "getString", "(I)Ljava/lang/String;", columnIndex );
407
    QString selectedImagePath = "file://" + result.toString();
408
    emit imageSelected( selectedImagePath );
409
  }
410
  else if ( receiverRequestCode == CAMERA_CODE && resultCode == RESULT_OK )
411
  {
412
    QJniObject RESULT_STRING = QJniObject::fromString( QStringLiteral( "__RESULT__" ) );
413
    QJniObject absolutePathJNI = data.callObjectMethod( "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;", RESULT_STRING.object<jstring>() );
414
    QString absolutePath = absolutePathJNI.toString();
415

416
    QString selectedImagePath = "file://" + absolutePath;
417

418
    emit imageSelected( absolutePath );
419
  }
420
  else
421
  {
422
    QString msg( "Something went wrong with media store activity" );
423
    qDebug() << msg;
424
    showToast( msg );
425
  }
426

427
}
428
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc