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

MerginMaps / input / 6428839644

06 Oct 2023 07:37AM UTC coverage: 62.247% (-0.02%) from 62.265%
6428839644

push

github

tomasMizera
update new string

7680 of 12338 relevant lines covered (62.25%)

105.22 hits per line

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

5.88
/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
bool AndroidUtils::requestNotificationPermission()
×
95
{
96
#ifdef ANDROID
97
  double buildVersion = QSysInfo::productVersion().toDouble();
98

99
  // POST_NOTIFICATIONS permission is available from Android 13+
100
  if ( buildVersion < ANDROID_VERSION_13 )
101
  {
102
    return true;
103
  }
104

105
  QString notificationPermission = QStringLiteral( "android.permission.POST_NOTIFICATIONS" );
106

107
  auto r = QtAndroidPrivate::checkPermission( notificationPermission ).result();
108
  if ( r == QtAndroidPrivate::Authorized )
109
  {
110
    return true;
111
  }
112

113
  r = QtAndroidPrivate::requestPermission( notificationPermission ).result();
114
  if ( r == QtAndroidPrivate::Authorized )
115
  {
116
    return true;
117
  }
118
#endif
119
  return false;
×
120
}
121

122
QString AndroidUtils::readExif( const QString &filePath, const QString &tag )
×
123
{
124
#ifdef ANDROID
125
  QJniObject jFilePath = QJniObject::fromString( filePath );
126
  QJniObject jTag = QJniObject::fromString( tag );
127
  QJniObject attribute = QJniObject::callStaticObjectMethod( "uk.co.lutraconsulting.EXIFUtils",
128
                         "getEXIFAttribute",
129
                         "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
130
                         jFilePath.object<jstring>(),
131
                         jTag.object<jstring>() );
132
  return attribute.toString();
133
#else
134
  Q_UNUSED( filePath )
135
  Q_UNUSED( tag )
136
  return QString();
×
137
#endif
138
}
139

140
void AndroidUtils::turnBluetoothOn()
×
141
{
142
#ifdef ANDROID
143
  if ( !isBluetoothTurnedOn() )
144
  {
145
    QJniObject ACTION_BT = QJniObject::getStaticObjectField(
146
                             "android/bluetooth/BluetoothAdapter",
147
                             "ACTION_REQUEST_ENABLE",
148
                             "Ljava/lang/String;"
149
                           );
150

151
    QJniObject intent(
152
      "android/content/Intent",
153
      "(Ljava/lang/String;)V",
154
      ACTION_BT.object()
155
    );
156

157
    if ( ACTION_BT.isValid() && intent.isValid() )
158
    {
159
      QtAndroidPrivate::startActivity( intent.object<jobject>(), BLUETOOTH_CODE, this );
160
    }
161
  }
162
#endif
163
}
×
164

165
bool AndroidUtils::isBluetoothTurnedOn()
×
166
{
167
#ifdef ANDROID
168
  return mBluetooth.hostMode() != QBluetoothLocalDevice::HostPoweredOff;
169
#else
170
  return true;
×
171
#endif
172
}
173

174
void AndroidUtils::quitApp()
×
175
{
176
#ifdef ANDROID
177
  auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
178
  activity.callMethod<void>( "quitGracefully", "()V" );
179

180
  // If quitGracefully failed or this device is not of specified manufacturer, let's exit via QT
181
  QCoreApplication::quit();
182
#endif
183
}
×
184

185
bool AndroidUtils::requestStoragePermission()
×
186
{
187
#ifdef ANDROID
188
  double buildVersion = QSysInfo::productVersion().toDouble();
189

190
  //
191
  // Android SDK 33 has a new set of permissions when reading external storage.
192
  // See https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE
193
  //
194
  QString storagePermissionType = QStringLiteral( "android.permission.READ_MEDIA_IMAGES" );
195
  if ( buildVersion < ANDROID_VERSION_13 )
196
  {
197
    storagePermissionType = QStringLiteral( "android.permission.READ_EXTERNAL_STORAGE" );
198
  }
199

200
  if ( !checkAndAcquirePermissions( storagePermissionType ) )
201
  {
202
    auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
203
    jboolean res = activity.callMethod<jboolean>( "shouldShowRequestPermissionRationale", "(Ljava/lang/String;)Z", QJniObject::fromString( "android.permission.WRITE_EXTERNAL_STORAGE" ).object() );
204
    if ( !res )
205
    {
206
      // permanently denied permission, user needs to go to settings to allow permission
207
      showToast( tr( "Storage permission is permanently denied, please allow it in settings in order to load pictures from gallery" ) );
208
    }
209
    else
210
    {
211
      showToast( tr( "Input needs a storage permission in order to load pictures from gallery" ) );
212
    }
213
    return false;
214
  }
215
#endif
216
  return true;
×
217
}
218

219
bool AndroidUtils::requestCameraPermission()
×
220
{
221
#ifdef ANDROID
222
  if ( checkAndAcquirePermissions( "android.permission.CAMERA" ) == false )
223
  {
224
    auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
225
    jboolean res = activity.callMethod<jboolean>( "shouldShowRequestPermissionRationale", "(Ljava/lang/String;)Z", QJniObject::fromString( "android.permission.CAMERA" ).object() );
226
    if ( !res )
227
    {
228
      // permanently denied permission, user needs to go to settings to allow permission
229
      showToast( tr( "Camera permission is permanently denied, please allow it in settings" ) );
230
    }
231
    else
232
    {
233
      showToast( tr( "We need a camera permission in order to take a photo" ) );
234
    }
235
    return false;
236
  }
237
#endif
238
  return true;
×
239
}
240

241
bool AndroidUtils::requestMediaLocationPermission()
×
242
{
243
#ifdef ANDROID
244
  // ACCESS_MEDIA_LOCATION is a runtime permission without UI dialog (User do not need to click anything to grant it, it is granted automatically)
245
  return checkAndAcquirePermissions( "android.permission.ACCESS_MEDIA_LOCATION" );
246
#endif
247
  return true;
×
248
}
249

250
void AndroidUtils::callImagePicker()
×
251
{
252
#ifdef ANDROID
253

254
  if ( !requestStoragePermission() )
255
  {
256
    return;
257
  }
258

259
  // request media location permission to be able to read EXIF metadata from gallery image
260
  // it is not a mandatory permission, so continue even if it is rejected
261
  requestMediaLocationPermission();
262

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

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

268
  if ( ACTION_PICK.isValid() && intent.isValid() )
269
  {
270
    intent.callObjectMethod( "setType", "(Ljava/lang/String;)Landroid/content/Intent;", QJniObject::fromString( "image/*" ).object<jstring>() );
271
    QtAndroidPrivate::startActivity( intent.object<jobject>(), MEDIA_CODE, this ); // this as receiver
272
  }
273
#endif
274
}
×
275

276
void AndroidUtils::installQRCodeScanner()
×
277
{
278
#ifdef ANDROID
279

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

283
  QJniObject packageName = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting" ) );
284
  QJniObject className = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.InstallScannerActivity" ) );
285

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

288
  QtAndroidPrivate::startActivity( intent, INSTALL_QR_SCANNER_CODE, this );
289
#endif
290
}
×
291

292
void AndroidUtils::scanQRCode()
×
293
{
294
#ifdef ANDROID
295

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

299
  QJniObject packageName = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting" ) );
300
  QJniObject className = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.ScannerActivity" ) );
301

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

304
  QtAndroidPrivate::startActivity( intent, QR_SCAN_CODE, this );
305
#endif
306
}
×
307

308
void AndroidUtils::callCamera( const QString &targetPath )
×
309
{
310
#ifdef ANDROID
311
  if ( !requestCameraPermission() )
312
  {
313
    return;
314
  }
315

316
  // request media location permission to be able to read EXIF metadata from captured image
317
  // it is not a mandatory permission, so continue even if it is rejected
318
  requestMediaLocationPermission();
319

320
  const QString IMAGE_CAPTURE_ACTION = QString( "android.media.action.IMAGE_CAPTURE" );
321

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

325
  QJniObject packageName = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting" ) );
326
  QJniObject className = QJniObject::fromString( QStringLiteral( "uk.co.lutraconsulting.CameraActivity" ) );
327

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

330
  QJniObject extra = QJniObject::fromString( "targetPath" );
331
  QJniObject my_prefix = QJniObject::fromString( targetPath );
332

333
  intent.callObjectMethod( "putExtra",
334
                           "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
335
                           extra.object<jstring>(),
336
                           my_prefix.object<jstring>() );
337

338
  if ( intent.isValid() )
339
  {
340
    QtAndroidPrivate::startActivity( intent.object<jobject>(), CAMERA_CODE, this );
341
  }
342
#else
343
  Q_UNUSED( targetPath )
344
#endif
345
}
×
346

347
#ifdef ANDROID
348
void AndroidUtils::handleActivityResult( int receiverRequestCode, int resultCode, const QJniObject &data )
349
{
350
  jint RESULT_OK = QJniObject::getStaticField<jint>( "android/app/Activity", "RESULT_OK" );
351
  jint RESULT_CANCELED = QJniObject::getStaticField<jint>( "android/app/Activity", "RESULT_CANCELED" );
352

353
  if ( receiverRequestCode == BLUETOOTH_CODE )
354
  {
355
    if ( resultCode == RESULT_OK )
356
    {
357
      emit bluetoothEnabled( true );
358
    }
359
    else
360
    {
361
      emit bluetoothEnabled( false );
362
    }
363

364
    return;
365
  }
366

367
  if ( receiverRequestCode == QR_SCAN_CODE )
368
  {
369
    const QJniObject key = QJniObject::fromString( "message" );
370
    const QJniObject rawResponse = data.callObjectMethod( "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;", key.object() );
371

372
    QString response;
373

374
    if ( rawResponse.isValid() )
375
    {
376
      response = rawResponse.toString();
377
    }
378

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

381
    if ( resultCode == RESULT_OK )
382
    {
383
      emit qrScanFinished( response );
384
    }
385
    else
386
    {
387
      if ( response == "not_installed" )
388
      {
389
        emit qrScannerMissing();
390
      }
391
      else
392
      {
393
        emit qrScanAborted();
394
      }
395
    }
396

397
    return;
398
  }
399

400
  if ( receiverRequestCode == INSTALL_QR_SCANNER_CODE )
401
  {
402
    return;
403
  }
404

405
  if ( resultCode == RESULT_CANCELED )
406
  {
407
    QJniObject RESULT_STRING = QJniObject::fromString( QStringLiteral( "__RESULT__" ) );
408
    // User has triggered cancel, result has no data.
409
    if ( !data.isValid() )
410
    {
411
      return;
412
    }
413

414
    QJniObject errorJNI = data.callObjectMethod( "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;", RESULT_STRING.object<jstring>() );
415
    // Internal cancelation due to an error
416
    QString errorMsg = errorJNI.toString();
417
    showToast( errorMsg );
418
    return;
419
  }
420

421
  if ( receiverRequestCode == MEDIA_CODE && resultCode == RESULT_OK )
422
  {
423
    QJniObject uri = data.callObjectMethod( "getData", "()Landroid/net/Uri;" );
424
    QJniObject mediaStore = QJniObject::getStaticObjectField( "android/provider/MediaStore$MediaColumns", "DATA", "Ljava/lang/String;" );
425
    QJniEnvironment env;
426
    jobjectArray projection = ( jobjectArray )env->NewObjectArray( 1, env->FindClass( "java/lang/String" ), NULL );
427
    jobject projectionDataAndroid = env->NewStringUTF( mediaStore.toString().toStdString().c_str() );
428
    env->SetObjectArrayElement( projection, 0, projectionDataAndroid );
429
    auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
430
    QJniObject contentResolver = activity.callObjectMethod( "getContentResolver", "()Landroid/content/ContentResolver;" );
431
    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 );
432
    jint columnIndex = cursor.callMethod<jint>( "getColumnIndex", "(Ljava/lang/String;)I", mediaStore.object<jstring>() );
433
    cursor.callMethod<jboolean>( "moveToFirst", "()Z" );
434
    QJniObject result = cursor.callObjectMethod( "getString", "(I)Ljava/lang/String;", columnIndex );
435
    QString selectedImagePath = "file://" + result.toString();
436
    emit imageSelected( selectedImagePath );
437
  }
438
  else if ( receiverRequestCode == CAMERA_CODE && resultCode == RESULT_OK )
439
  {
440
    QJniObject RESULT_STRING = QJniObject::fromString( QStringLiteral( "__RESULT__" ) );
441
    QJniObject absolutePathJNI = data.callObjectMethod( "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;", RESULT_STRING.object<jstring>() );
442
    QString absolutePath = absolutePathJNI.toString();
443

444
    QString selectedImagePath = "file://" + absolutePath;
445

446
    emit imageSelected( absolutePath );
447
  }
448
  else
449
  {
450
    QString msg( "Something went wrong with media store activity" );
451
    qDebug() << msg;
452
    showToast( msg );
453
  }
454

455
}
456
#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