367 template <
typename>
struct VersionTag {
int version; };
372 String getMimeTypeFromExtension (
const String& str)
const
374 const auto javaStr = javaString (str);
375 return juceString ((jstring) getEnv()->CallObjectMethod (map.get(),
376 AndroidMimeTypeMap.getMimeTypeFromExtension,
380 String getExtensionFromMimeType (
const String& str)
const
382 const auto javaStr = javaString (str);
383 return juceString ((jstring) getEnv()->CallObjectMethod (map.get(),
384 AndroidMimeTypeMap.getExtensionFromMimeType,
389 GlobalRef map { LocalRef<jobject> { getEnv()->CallStaticObjectMethod (AndroidMimeTypeMap,
390 AndroidMimeTypeMap.getSingleton) } };
393 class AndroidDocumentPimplApi19 :
public Pimpl
396 AndroidDocumentPimplApi19() =
default;
398 explicit AndroidDocumentPimplApi19 (
const URL& uriIn)
399 : AndroidDocumentPimplApi19 (urlToUri (uriIn)) {}
401 explicit AndroidDocumentPimplApi19 (
const LocalRef<jobject>& uriIn)
404 std::unique_ptr<Pimpl> clone()
const override {
return std::make_unique<AndroidDocumentPimplApi19> (*
this); }
406 bool deleteDocument()
const override
408 if (
const auto resolver = AndroidContentUriResolver::getContentResolver())
410 return getEnv()->CallStaticBooleanMethod (DocumentsContract19,
411 DocumentsContract19.deleteDocument,
421 auto result = std::make_unique<AndroidContentUriInputStream> (uri);
422 return result->openedSuccessfully() ? std::move (result) :
nullptr;
427 auto stream = AndroidStreamHelpers::createStream (uri, AndroidStreamHelpers::StreamKind::output);
429 return stream.get() !=
nullptr ? std::make_unique<AndroidContentUriOutputStream> (std::move (stream))
437 auto getColumnNames()
const
439 return Detail::makeStringArray (flagsColumn, nameColumn, mimeColumn, idColumn, modifiedColumn, sizeColumn);
442 auto readFromCursor (jobject cursor)
const
444 auto* env = getEnv();
446 const auto flagsColumnIndex = env->CallIntMethod (cursor, AndroidCursor.getColumnIndex, flagsColumn.get());
447 const auto nameColumnIndex = env->CallIntMethod (cursor, AndroidCursor.getColumnIndex, nameColumn.get());
448 const auto mimeColumnIndex = env->CallIntMethod (cursor, AndroidCursor.getColumnIndex, mimeColumn.get());
449 const auto idColumnIndex = env->CallIntMethod (cursor, AndroidCursor.getColumnIndex, idColumn.get());
450 const auto modColumnIndex = env->CallIntMethod (cursor, AndroidCursor.getColumnIndex, modifiedColumn.get());
451 const auto sizeColumnIndex = env->CallIntMethod (cursor, AndroidCursor.getColumnIndex, sizeColumn.get());
453 const auto indices = { flagsColumnIndex, nameColumnIndex, mimeColumnIndex, idColumnIndex, modColumnIndex, sizeColumnIndex };
455 if (
std::any_of (indices.begin(), indices.end(), [] (
auto index) { return index < 0; }))
458 const LocalRef<jstring> nameString { (jstring) env->CallObjectMethod (cursor, AndroidCursor.getString, nameColumnIndex) };
459 const LocalRef<jstring> mimeString { (jstring) env->CallObjectMethod (cursor, AndroidCursor.getString, mimeColumnIndex) };
461 const auto readOpt = [&] (
int column) ->
Detail::Opt
463 const auto missing = env->CallBooleanMethod (cursor, AndroidCursor.isNull, column);
468 return Detail::Opt { env->CallLongMethod (cursor, AndroidCursor.getLong, column) };
472 .withType (juceString (mimeString.get()))
473 .withFlags (env->CallIntMethod (cursor, AndroidCursor.getInt, flagsColumnIndex))
474 .withModified (readOpt (modColumnIndex))
475 .withSize (readOpt (sizeColumnIndex));
478 GlobalRefImpl<jstring> flagsColumn { javaString (
"flags") };
479 GlobalRefImpl<jstring> nameColumn { javaString (
"_display_name") };
480 GlobalRefImpl<jstring> mimeColumn { javaString (
"mime_type") };
481 GlobalRefImpl<jstring> idColumn { javaString (
"document_id") };
482 GlobalRefImpl<jstring> modifiedColumn { javaString (
"last_modified") };
483 GlobalRefImpl<jstring> sizeColumn { javaString (
"_size") };
486 Detail::AndroidIteratorEngine<Columns> iterator { Columns{}, uri };
488 if (! iterator.increment())
491 auto* env = getEnv();
492 auto ctx = getAppContext();
494 const auto hasPermission = [&] (
auto permission)
496 return env->CallIntMethod (ctx, AndroidContext.checkCallingOrSelfUriPermission, uri.get(), permission) == 0;
499 return iterator.read()
500 .withReadPermission (hasPermission (Detail::FLAG_GRANT_READ_URI_PERMISSION))
501 .withWritePermission (hasPermission (Detail::FLAG_GRANT_WRITE_URI_PERMISSION))
505 URL getUrl()
const override
507 return Detail::uriToUrl (uri);
510 NativeInfo getNativeInfo()
const override {
return { uri }; }
517 class AndroidDocumentPimplApi21 :
public AndroidDocumentPimplApi19
520 using AndroidDocumentPimplApi19::AndroidDocumentPimplApi19;
522 std::unique_ptr<Pimpl> clone()
const override {
return std::make_unique<AndroidDocumentPimplApi21> (*
this); }
526 return Utils::createPimplForSdk (LocalRef<jobject> { getEnv()->CallStaticObjectMethod (DocumentsContract21,
527 DocumentsContract21.createDocument,
528 AndroidContentUriResolver::getContentResolver().get(),
529 getNativeInfo().uri.get(),
530 javaString (type).get(),
531 javaString (name).get()) });
536 if (
const auto resolver = AndroidContentUriResolver::getContentResolver())
538 return Utils::createPimplForSdk (LocalRef<jobject> { getEnv()->CallStaticObjectMethod (DocumentsContract21,
539 DocumentsContract21.renameDocument,
541 getNativeInfo().uri.get(),
542 javaString (name).get()) });
550 class AndroidDocumentPimplApi24 final :
public AndroidDocumentPimplApi21
553 using AndroidDocumentPimplApi21::AndroidDocumentPimplApi21;
555 std::unique_ptr<Pimpl> clone()
const override {
return std::make_unique<AndroidDocumentPimplApi24> (*
this); }
559 if (target.getNativeInfo().uri ==
nullptr)
565 return Utils::createPimplForSdk (LocalRef<jobject> { getEnv()->CallStaticObjectMethod (DocumentsContract24,
566 DocumentsContract24.copyDocument,
567 AndroidContentUriResolver::getContentResolver().get(),
568 getNativeInfo().uri.get(),
569 target.getNativeInfo().uri.get()) });
574 if (currentParent.getNativeInfo().uri ==
nullptr || newParent.getNativeInfo().uri ==
nullptr)
580 return Utils::createPimplForSdk (LocalRef<jobject> { getEnv()->CallStaticObjectMethod (DocumentsContract24,
581 DocumentsContract24.moveDocument,
582 AndroidContentUriResolver::getContentResolver().get(),
583 getNativeInfo().uri.get(),
584 currentParent.getNativeInfo().uri.get(),
585 newParent.getNativeInfo().uri.get()) });
591 if (jniCheckHasExceptionOccurredAndClear())
594 return createPimplForSdkImpl (uri,
595 VersionTag<AndroidDocumentPimplApi24> { 24 },
596 VersionTag<AndroidDocumentPimplApi21> { 21 },
597 VersionTag<AndroidDocumentPimplApi19> { 19 });
607 template <
typename T,
typename... Ts>
610 VersionTag<Ts>... tail)
612 if (head.version <= getAndroidSDKVersion())
613 return std::make_unique<T> (uri);
615 return createPimplForSdkImpl (uri, tail...);
622 static String getMimeTypeFromExtension (
const String& str)
624 return detail::MimeTypeTable::getMimeTypesForFileExtension (str)[0];
627 static String getExtensionFromMimeType (
const String& str)
629 return detail::MimeTypeTable::getFileExtensionsForMimeType (str)[0];
643 std::unique_ptr<Pimpl> clone()
const override {
return std::make_unique<AndroidDocumentPimplFile> (*
this); }
645 bool deleteDocument()
const override
647 return file.deleteRecursively (
false);
652 const auto target = file.getSiblingFile (name);
654 return file.moveFileTo (target) ? std::make_unique<AndroidDocumentPimplFile> (target)
662 auto result = file.createOutputStream();
663 result->setPosition (0);
670 const auto parent = target.getFile();
672 if (parent ==
File())
675 const auto actual = parent.
getChildFile (file.getFileName());
680 const auto success = file.
isDirectory() ? file.copyDirectoryTo (actual)
681 : file.copyFileTo (actual);
683 return success ? std::make_unique<AndroidDocumentPimplFile> (actual)
688 const String& name)
const override
690 const auto extension = mimeConverter.getExtensionFromMimeType (type);
691 const auto target = file.getChildFile (extension.isNotEmpty() ? name +
"." + extension : name);
693 if (! target.exists() && (type == Detail::dirMime ? target.createDirectory() : target.create()))
694 return std::make_unique<AndroidDocumentPimplFile> (target);
700 const Pimpl& newParentPimpl)
const override
702 const auto currentParent = currentParentPimpl.getFile();
703 const auto newParent = newParentPimpl.getFile();
705 if (! file.isAChildOf (currentParent) || newParent ==
File())
708 const auto target = newParent.getChildFile (file.getFileName());
710 if (target.exists() || ! file.moveFileTo (target))
713 return std::make_unique<AndroidDocumentPimplFile> (target);
721 const auto size = file.getSize();
722 const auto extension = file.getFileExtension().removeCharacters (
".").toLowerCase();
723 const auto type = file.isDirectory() ? Detail::dirMime
724 : mimeConverter.getMimeTypeFromExtension (extension);
727 .withType (type.
isNotEmpty() ? type :
"application/octet-stream")
728 .withFlags (AndroidDocumentInfo::Args::getFlagsForFile (file))
729 .withModified (
Detail::Opt { file.getLastModificationTime().toMilliseconds() })
731 .withReadPermission (file.hasReadAccess())
732 .withWritePermission (file.hasWriteAccess())
736 URL getUrl()
const override {
return URL (file); }
738 NativeInfo getNativeInfo()
const override {
return {}; }