JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_AndroidDocument_android.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26/* This is mainly used to pass implementation information from AndroidDocument to
27 AndroidDocumentIterator. This needs to be defined in a .cpp because it uses the internal
28 GlobalRef type.
29
30 To preserve encapsulation, this struct should only contain information that would normally be
31 public, were internal types not in use.
32*/
34{
35 #if JUCE_ANDROID
37 #endif
38};
39
40//==============================================================================
42{
43 ~AndroidDocumentDetail() = delete; // This struct is a single-file namespace
44
45 struct Opt
46 {
47 Opt() = default;
48
49 explicit Opt (int64 v) : value (v), valid (true) {}
50
51 int64 value = 0;
52 bool valid = false;
53 };
54
55 static constexpr auto dirMime = "vnd.android.document/directory";
56
57 #if JUCE_ANDROID
58 /*
59 A very basic type that acts a bit like an iterator, in that it can be incremented, and read-from.
60
61 Instances of this type can be passed to the constructor of AndroidDirectoryIterator to provide
62 stdlib-like iterator facilities.
63 */
64 template <typename Columns>
66 {
67 public:
68 AndroidIteratorEngine (Columns columnsIn, jobject uri)
69 : columns (std::move (columnsIn)),
70 cursor { LocalRef<jobject> { getEnv()->CallObjectMethod (AndroidContentUriResolver::getContentResolver().get(),
71 ContentResolver.query,
72 uri,
73 columns.getColumnNames().get(),
74 nullptr,
75 nullptr,
76 nullptr) } }
77 {
78 // Creating the cursor may throw if the document doesn't exist.
79 // In that case, cursor will still be null.
81 }
82
83 auto read() const { return columns.readFromCursor (cursor.get()); }
84
85 bool increment()
86 {
87 if (cursor.get() == nullptr)
88 return false;
89
90 return getEnv()->CallBooleanMethod (cursor.get(), AndroidCursor.moveToNext);
91 }
92
93 private:
94 Columns columns;
95 GlobalRef cursor;
96 };
97
98 template <typename... Args, size_t... Ix>
100 {
101 auto* env = getEnv();
102 LocalRef<jobjectArray> array { env->NewObjectArray (sizeof... (args), JavaString, nullptr) };
103
104 (env->SetObjectArrayElement (array.get(), Ix, args.get()), ...);
105
106 return array;
107 }
108
109 template <typename... Args>
110 static LocalRef<jobjectArray> makeStringArray (Args&&... args)
111 {
112 return makeStringArray (std::make_index_sequence<sizeof... (args)>(), std::forward<Args> (args)...);
113 }
114
115 static URL uriToUrl (jobject uri)
116 {
117 return URL (juceString ((jstring) getEnv()->CallObjectMethod (uri, AndroidUri.toString)));
118 }
119
120 struct Columns
121 {
124
125 auto getColumnNames() const
126 {
127 return makeStringArray (idColumn);
128 }
129
130 auto readFromCursor (jobject cursor) const
131 {
132 auto* env = getEnv();
133 const auto idColumnIndex = env->CallIntMethod (cursor, AndroidCursor.getColumnIndex, idColumn.get());
134
135 const auto documentUri = [&]
136 {
137 if (idColumnIndex < 0)
138 return LocalRef<jobject>{};
139
140 LocalRef<jstring> documentId { (jstring) env->CallObjectMethod (cursor, AndroidCursor.getString, idColumnIndex) };
141 return LocalRef<jobject> { getEnv()->CallStaticObjectMethod (DocumentsContract21,
142 DocumentsContract21.buildDocumentUriUsingTree,
143 treeUri.get(),
144 documentId.get()) };
145 }();
146
148 }
149 };
150
152
154 {
155 const LocalRef <jobject> documentId { getEnv()->CallStaticObjectMethod (DocumentsContract19,
156 DocumentsContract19.getDocumentId,
157 uri.get()) };
158 const LocalRef <jobject> childrenUri { getEnv()->CallStaticObjectMethod (DocumentsContract21,
159 DocumentsContract21.buildChildDocumentsUriUsingTree,
160 uri.get(),
161 documentId.get()) };
162
164 GlobalRefImpl<jstring> { javaString ("document_id") } },
165 childrenUri.get() };
166 }
167
168 class RecursiveEngine
169 {
170 public:
171 explicit RecursiveEngine (GlobalRef uri)
173
174 AndroidDocument read() const
175 {
176 return subIterator != nullptr ? subIterator->read() : engine.read();
177 }
178
179 bool increment()
180 {
181 if (directory && subIterator == nullptr)
182 subIterator = std::make_unique<RecursiveEngine> (engine.read().getNativeInfo().uri);
183
184 if (subIterator != nullptr)
185 {
186 if (subIterator->increment())
187 return true;
188
189 subIterator = nullptr;
190 }
191
192 if (! engine.increment())
193 return false;
194
195 directory = engine.read().getInfo().isDirectory();
196 return true;
197 }
198
199 private:
202 bool directory = false;
203 };
204
206
207 static void setPermissions (const URL& url, jmethodID func)
208 {
209 if (getAndroidSDKVersion() < 19)
210 return;
211
212 const auto javaUri = urlToUri (url);
213
214 if (const auto resolver = AndroidContentUriResolver::getContentResolver())
215 {
217 getEnv()->CallVoidMethod (resolver, func, javaUri.get(), flags);
219 }
220 }
221 #endif
222
224 {
225 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
226 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996)
227 DirectoryIteratorEngine (const File& dir, bool recursive)
228 : iterator (dir, recursive, "*", File::findFilesAndDirectories) {}
229 JUCE_END_IGNORE_WARNINGS_MSVC
230 JUCE_END_IGNORE_WARNINGS_GCC_LIKE
231
232 auto read() const { return AndroidDocument::fromFile (iterator.getFile()); }
233 bool increment() { return iterator.next(); }
234 DirectoryIterator iterator;
235 };
236
237};
238
239//==============================================================================
241{
242public:
244
245 Args withName (String x) const { return with (&Args::name, std::move (x)); }
246 Args withType (String x) const { return with (&Args::type, std::move (x)); }
247 Args withFlags (int x) const { return with (&Args::flags, x); }
248 Args withSize (Detail::Opt x) const { return with (&Args::sizeInBytes, x); }
249 Args withModified (Detail::Opt x) const { return with (&Args::lastModified, x); }
250 Args withReadPermission (bool x) const { return with (&Args::readPermission, x); }
251 Args withWritePermission (bool x) const { return with (&Args::writePermission, x); }
252
253 String name;
254 String type;
255 Detail::Opt sizeInBytes, lastModified;
256 int flags = 0;
257 bool readPermission = false, writePermission = false;
258
259 static int getFlagsForFile (const File& file)
260 {
261 int flags = 0;
262
263 if (file.hasReadAccess())
264 flags |= AndroidDocumentInfo::flagSupportsCopy;
265
266 if (file.hasWriteAccess())
267 flags |= AndroidDocumentInfo::flagSupportsWrite
268 | AndroidDocumentInfo::flagDirSupportsCreate
269 | AndroidDocumentInfo::flagSupportsMove
270 | AndroidDocumentInfo::flagSupportsRename
271 | AndroidDocumentInfo::flagSupportsDelete;
272
273 return flags;
274 }
275
276 AndroidDocumentInfo build() const
277 {
278 return AndroidDocumentInfo (*this);
279 }
280
281private:
282 template <typename Value>
283 Args with (Value Args::* member, Value v) const
284 {
285 auto copy = *this;
286 copy.*member = std::move (v);
287 return copy;
288 }
289};
290
291AndroidDocumentInfo::AndroidDocumentInfo (Args args)
292 : name (args.name),
293 type (args.type),
294 lastModified (args.lastModified.value),
295 sizeInBytes (args.sizeInBytes.value),
296 nativeFlags (args.flags),
297 juceFlags (flagExists
298 | (args.lastModified.valid ? flagValidModified : 0)
299 | (args.sizeInBytes.valid ? flagValidSize : 0)
300 | (args.readPermission ? flagHasReadPermission : 0)
301 | (args.writePermission ? flagHasWritePermission : 0))
302{
303}
304
305bool AndroidDocumentInfo::isDirectory() const { return type == AndroidDocumentDetail::dirMime; }
306
307//==============================================================================
309{
310public:
311 Pimpl() = default;
312 Pimpl (const Pimpl&) = default;
313 Pimpl (Pimpl&&) noexcept = default;
314 Pimpl& operator= (const Pimpl&) = default;
315 Pimpl& operator= (Pimpl&&) noexcept = default;
316
317 virtual ~Pimpl() = default;
318 virtual std::unique_ptr<Pimpl> clone() const = 0;
319 virtual bool deleteDocument() const = 0;
320 virtual std::unique_ptr<InputStream> createInputStream() const = 0;
321 virtual std::unique_ptr<OutputStream> createOutputStream() const = 0;
322 virtual AndroidDocumentInfo getInfo() const = 0;
323 virtual URL getUrl() const = 0;
324 virtual NativeInfo getNativeInfo() const = 0;
325
326 virtual std::unique_ptr<Pimpl> copyDocumentToParentDocument (const Pimpl&) const
327 {
328 // This function is not supported on the current platform.
330 return {};
331 }
332
333 virtual std::unique_ptr<Pimpl> moveDocumentFromParentToParent (const Pimpl&, const Pimpl&) const
334 {
335 // This function is not supported on the current platform.
337 return {};
338 }
339
340 virtual std::unique_ptr<Pimpl> renameTo (const String&) const
341 {
342 // This function is not supported on the current platform.
344 return {};
345 }
346
347 virtual std::unique_ptr<Pimpl> createChildDocumentWithTypeAndName (const String&, const String&) const
348 {
349 // This function is not supported on the current platform.
351 return {};
352 }
353
354 File getFile() const { return getUrl().getLocalFile(); }
355
356 static const Pimpl& getPimpl (const AndroidDocument& doc) { return *doc.pimpl; }
357};
358
359//==============================================================================
361{
363
364 ~Utils() = delete; // This stuct is a single-file namespace
365
366 #if JUCE_ANDROID
367 template <typename> struct VersionTag { int version; };
368
369 class MimeConverter
370 {
371 public:
372 String getMimeTypeFromExtension (const String& str) const
373 {
374 const auto javaStr = javaString (str);
375 return juceString ((jstring) getEnv()->CallObjectMethod (map.get(),
376 AndroidMimeTypeMap.getMimeTypeFromExtension,
377 javaStr.get()));
378 }
379
380 String getExtensionFromMimeType (const String& str) const
381 {
382 const auto javaStr = javaString (str);
383 return juceString ((jstring) getEnv()->CallObjectMethod (map.get(),
384 AndroidMimeTypeMap.getExtensionFromMimeType,
385 javaStr.get()));
386 }
387
388 private:
389 GlobalRef map { LocalRef<jobject> { getEnv()->CallStaticObjectMethod (AndroidMimeTypeMap,
390 AndroidMimeTypeMap.getSingleton) } };
391 };
392
393 class AndroidDocumentPimplApi19 : public Pimpl
394 {
395 public:
396 AndroidDocumentPimplApi19() = default;
397
398 explicit AndroidDocumentPimplApi19 (const URL& uriIn)
399 : AndroidDocumentPimplApi19 (urlToUri (uriIn)) {}
400
401 explicit AndroidDocumentPimplApi19 (const LocalRef<jobject>& uriIn)
402 : uri (uriIn) {}
403
404 std::unique_ptr<Pimpl> clone() const override { return std::make_unique<AndroidDocumentPimplApi19> (*this); }
405
406 bool deleteDocument() const override
407 {
408 if (const auto resolver = AndroidContentUriResolver::getContentResolver())
409 {
410 return getEnv()->CallStaticBooleanMethod (DocumentsContract19,
411 DocumentsContract19.deleteDocument,
412 resolver.get(),
413 uri.get());
414 }
415
416 return false;
417 }
418
419 std::unique_ptr<InputStream> createInputStream() const override
420 {
421 auto result = std::make_unique<AndroidContentUriInputStream> (uri);
422 return result->openedSuccessfully() ? std::move (result) : nullptr;
423 }
424
425 std::unique_ptr<OutputStream> createOutputStream() const override
426 {
427 auto stream = AndroidStreamHelpers::createStream (uri, AndroidStreamHelpers::StreamKind::output);
428
429 return stream.get() != nullptr ? std::make_unique<AndroidContentUriOutputStream> (std::move (stream))
430 : nullptr;
431 }
432
433 AndroidDocumentInfo getInfo() const override
434 {
435 struct Columns
436 {
437 auto getColumnNames() const
438 {
439 return Detail::makeStringArray (flagsColumn, nameColumn, mimeColumn, idColumn, modifiedColumn, sizeColumn);
440 }
441
442 auto readFromCursor (jobject cursor) const
443 {
444 auto* env = getEnv();
445
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());
452
453 const auto indices = { flagsColumnIndex, nameColumnIndex, mimeColumnIndex, idColumnIndex, modColumnIndex, sizeColumnIndex };
454
455 if (std::any_of (indices.begin(), indices.end(), [] (auto index) { return index < 0; }))
457
458 const LocalRef<jstring> nameString { (jstring) env->CallObjectMethod (cursor, AndroidCursor.getString, nameColumnIndex) };
459 const LocalRef<jstring> mimeString { (jstring) env->CallObjectMethod (cursor, AndroidCursor.getString, mimeColumnIndex) };
460
461 const auto readOpt = [&] (int column) -> Detail::Opt
462 {
463 const auto missing = env->CallBooleanMethod (cursor, AndroidCursor.isNull, column);
464
465 if (missing)
466 return {};
467
468 return Detail::Opt { env->CallLongMethod (cursor, AndroidCursor.getLong, column) };
469 };
470
471 return AndroidDocumentInfo::Args{}.withName (juceString (nameString.get()))
472 .withType (juceString (mimeString.get()))
473 .withFlags (env->CallIntMethod (cursor, AndroidCursor.getInt, flagsColumnIndex))
474 .withModified (readOpt (modColumnIndex))
475 .withSize (readOpt (sizeColumnIndex));
476 }
477
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") };
484 };
485
486 Detail::AndroidIteratorEngine<Columns> iterator { Columns{}, uri };
487
488 if (! iterator.increment())
489 return AndroidDocumentInfo{};
490
491 auto* env = getEnv();
492 auto ctx = getAppContext();
493
494 const auto hasPermission = [&] (auto permission)
495 {
496 return env->CallIntMethod (ctx, AndroidContext.checkCallingOrSelfUriPermission, uri.get(), permission) == 0;
497 };
498
499 return iterator.read()
500 .withReadPermission (hasPermission (Detail::FLAG_GRANT_READ_URI_PERMISSION))
501 .withWritePermission (hasPermission (Detail::FLAG_GRANT_WRITE_URI_PERMISSION))
502 .build();
503 }
504
505 URL getUrl() const override
506 {
507 return Detail::uriToUrl (uri);
508 }
509
510 NativeInfo getNativeInfo() const override { return { uri }; }
511
512 private:
513 GlobalRef uri;
514 };
515
516 //==============================================================================
517 class AndroidDocumentPimplApi21 : public AndroidDocumentPimplApi19
518 {
519 public:
520 using AndroidDocumentPimplApi19::AndroidDocumentPimplApi19;
521
522 std::unique_ptr<Pimpl> clone() const override { return std::make_unique<AndroidDocumentPimplApi21> (*this); }
523
524 std::unique_ptr<Pimpl> createChildDocumentWithTypeAndName (const String& type, const String& name) const override
525 {
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()) });
532 }
533
534 std::unique_ptr<Pimpl> renameTo (const String& name) const override
535 {
536 if (const auto resolver = AndroidContentUriResolver::getContentResolver())
537 {
538 return Utils::createPimplForSdk (LocalRef<jobject> { getEnv()->CallStaticObjectMethod (DocumentsContract21,
539 DocumentsContract21.renameDocument,
540 resolver.get(),
541 getNativeInfo().uri.get(),
542 javaString (name).get()) });
543 }
544
545 return nullptr;
546 }
547 };
548
549 //==============================================================================
550 class AndroidDocumentPimplApi24 final : public AndroidDocumentPimplApi21
551 {
552 public:
553 using AndroidDocumentPimplApi21::AndroidDocumentPimplApi21;
554
555 std::unique_ptr<Pimpl> clone() const override { return std::make_unique<AndroidDocumentPimplApi24> (*this); }
556
557 std::unique_ptr<Pimpl> copyDocumentToParentDocument (const Pimpl& target) const override
558 {
559 if (target.getNativeInfo().uri == nullptr)
560 {
561 // Cannot copy to a non-URI-based AndroidDocument
562 return {};
563 }
564
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()) });
570 }
571
572 std::unique_ptr<Pimpl> moveDocumentFromParentToParent (const Pimpl& currentParent, const Pimpl& newParent) const override
573 {
574 if (currentParent.getNativeInfo().uri == nullptr || newParent.getNativeInfo().uri == nullptr)
575 {
576 // Cannot move document between non-URI-based AndroidDocuments
577 return {};
578 }
579
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()) });
586 }
587 };
588
589 static std::unique_ptr<Pimpl> createPimplForSdk (const LocalRef<jobject>& uri)
590 {
591 if (jniCheckHasExceptionOccurredAndClear())
592 return nullptr;
593
594 return createPimplForSdkImpl (uri,
595 VersionTag<AndroidDocumentPimplApi24> { 24 },
596 VersionTag<AndroidDocumentPimplApi21> { 21 },
597 VersionTag<AndroidDocumentPimplApi19> { 19 });
598 }
599
600 static std::unique_ptr<Pimpl> createPimplForSdkImpl (const LocalRef<jobject>&)
601 {
602 // Failed to find a suitable implementation for this platform
604 return nullptr;
605 }
606
607 template <typename T, typename... Ts>
608 static std::unique_ptr<Pimpl> createPimplForSdkImpl (const LocalRef<jobject>& uri,
609 VersionTag<T> head,
610 VersionTag<Ts>... tail)
611 {
612 if (head.version <= getAndroidSDKVersion())
613 return std::make_unique<T> (uri);
614
615 return createPimplForSdkImpl (uri, tail...);
616 }
617
618 #else
620 {
621 public:
622 static String getMimeTypeFromExtension (const String& str)
623 {
624 return detail::MimeTypeTable::getMimeTypesForFileExtension (str)[0];
625 }
626
627 static String getExtensionFromMimeType (const String& str)
628 {
629 return detail::MimeTypeTable::getFileExtensionsForMimeType (str)[0];
630 }
631 };
632 #endif
633
634 //==============================================================================
635 class AndroidDocumentPimplFile final : public Pimpl
636 {
637 public:
638 explicit AndroidDocumentPimplFile (const File& f)
639 : file (f)
640 {
641 }
642
643 std::unique_ptr<Pimpl> clone() const override { return std::make_unique<AndroidDocumentPimplFile> (*this); }
644
645 bool deleteDocument() const override
646 {
647 return file.deleteRecursively (false);
648 }
649
650 std::unique_ptr<Pimpl> renameTo (const String& name) const override
651 {
652 const auto target = file.getSiblingFile (name);
653
654 return file.moveFileTo (target) ? std::make_unique<AndroidDocumentPimplFile> (target)
655 : nullptr;
656 }
657
658 std::unique_ptr<InputStream> createInputStream() const override { return file.createInputStream(); }
659
660 std::unique_ptr<OutputStream> createOutputStream() const override
661 {
662 auto result = file.createOutputStream();
663 result->setPosition (0);
664 result->truncate();
665 return result;
666 }
667
668 std::unique_ptr<Pimpl> copyDocumentToParentDocument (const Pimpl& target) const override
669 {
670 const auto parent = target.getFile();
671
672 if (parent == File())
673 return nullptr;
674
675 const auto actual = parent.getChildFile (file.getFileName());
676
677 if (actual.exists())
678 return nullptr;
679
680 const auto success = file.isDirectory() ? file.copyDirectoryTo (actual)
681 : file.copyFileTo (actual);
682
683 return success ? std::make_unique<AndroidDocumentPimplFile> (actual)
684 : nullptr;
685 }
686
687 std::unique_ptr<Pimpl> createChildDocumentWithTypeAndName (const String& type,
688 const String& name) const override
689 {
690 const auto extension = mimeConverter.getExtensionFromMimeType (type);
691 const auto target = file.getChildFile (extension.isNotEmpty() ? name + "." + extension : name);
692
693 if (! target.exists() && (type == Detail::dirMime ? target.createDirectory() : target.create()))
694 return std::make_unique<AndroidDocumentPimplFile> (target);
695
696 return nullptr;
697 }
698
699 std::unique_ptr<Pimpl> moveDocumentFromParentToParent (const Pimpl& currentParentPimpl,
700 const Pimpl& newParentPimpl) const override
701 {
702 const auto currentParent = currentParentPimpl.getFile();
703 const auto newParent = newParentPimpl.getFile();
704
705 if (! file.isAChildOf (currentParent) || newParent == File())
706 return nullptr;
707
708 const auto target = newParent.getChildFile (file.getFileName());
709
710 if (target.exists() || ! file.moveFileTo (target))
711 return nullptr;
712
713 return std::make_unique<AndroidDocumentPimplFile> (target);
714 }
715
716 AndroidDocumentInfo getInfo() const override
717 {
718 if (! file.exists())
719 return AndroidDocumentInfo{};
720
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);
725
726 return AndroidDocumentInfo::Args{}.withName (file.getFileName())
727 .withType (type.isNotEmpty() ? type : "application/octet-stream")
728 .withFlags (AndroidDocumentInfo::Args::getFlagsForFile (file))
729 .withModified (Detail::Opt { file.getLastModificationTime().toMilliseconds() })
730 .withSize (size != 0 ? Detail::Opt { size } : Detail::Opt{})
731 .withReadPermission (file.hasReadAccess())
732 .withWritePermission (file.hasWriteAccess())
733 .build();
734 }
735
736 URL getUrl() const override { return URL (file); }
737
738 NativeInfo getNativeInfo() const override { return {}; }
739
740 private:
741 File file;
742 MimeConverter mimeConverter;
743 };
744};
745
746//==============================================================================
748{
749 #if JUCE_ANDROID
750 AndroidDocumentDetail::setPermissions (url, ContentResolver19.takePersistableUriPermission);
751 #endif
752}
753
755{
756 #if JUCE_ANDROID
757 AndroidDocumentDetail::setPermissions (url, ContentResolver19.releasePersistableUriPermission);
758 #endif
759}
760
762{
763 #if ! JUCE_ANDROID
764 return {};
765 #else
766 if (getAndroidSDKVersion() < 19)
767 return {};
768
769 auto* env = getEnv();
770 const LocalRef<jobject> permissions { env->CallObjectMethod (AndroidContentUriResolver::getContentResolver().get(),
771 ContentResolver19.getPersistedUriPermissions) };
772
773 if (permissions == nullptr)
774 return {};
775
777 const auto size = env->CallIntMethod (permissions, JavaList.size);
778
779 for (auto i = (decltype (size)) 0; i < size; ++i)
780 {
781 const LocalRef<jobject> uriPermission { env->CallObjectMethod (permissions, JavaList.get, i) };
782
784 permission.time = env->CallLongMethod (uriPermission, AndroidUriPermission.getPersistedTime);
785 permission.read = env->CallBooleanMethod (uriPermission, AndroidUriPermission.isReadPermission);
786 permission.write = env->CallBooleanMethod (uriPermission, AndroidUriPermission.isWritePermission);
787 permission.url = AndroidDocumentDetail::uriToUrl (env->CallObjectMethod (uriPermission, AndroidUriPermission.getUri));
788
789 result.push_back (std::move (permission));
790 }
791
792 return result;
793 #endif
794}
795
796//==============================================================================
798
800{
801 #if JUCE_ANDROID
802 const LocalRef<jobject> info { getEnv()->CallObjectMethod (getAppContext(), AndroidContext.getApplicationInfo) };
803 const auto targetSdkVersion = getEnv()->GetIntField (info.get(), AndroidApplicationInfo.targetSdkVersion);
804
805 // At the current API level, plain file paths may not work for accessing files in shared
806 // locations. It's recommended to use fromDocument() or fromTree() instead when targeting this
807 // API level.
809 #endif
810
811 return AndroidDocument { filePath != File() ? std::make_unique<Utils::AndroidDocumentPimplFile> (filePath)
812 : nullptr };
813}
814
816{
817 #if JUCE_ANDROID
818 if (getAndroidSDKVersion() < 19)
819 {
820 // This function is unsupported on this platform.
822 return AndroidDocument{};
823 }
824
825 const auto javaUri = urlToUri (documentUrl);
826
828 DocumentsContract19.isDocumentUri,
829 getAppContext().get(),
830 javaUri.get()))
831 {
832 return AndroidDocument{};
833 }
834
835 return AndroidDocument { Utils::createPimplForSdk (javaUri) };
836 #else
837 return AndroidDocument{};
838 #endif
839}
840
842{
843 #if JUCE_ANDROID
844 if (getAndroidSDKVersion() < 21)
845 {
846 // This function is unsupported on this platform.
848 return AndroidDocument{};
849 }
850
851 const auto javaUri = urlToUri (treeUrl);
852 LocalRef<jobject> treeDocumentId { getEnv()->CallStaticObjectMethod (DocumentsContract21,
853 DocumentsContract21.getTreeDocumentId,
854 javaUri.get()) };
855
857
858 if (treeDocumentId == nullptr)
859 {
861 return AndroidDocument{};
862 }
863
864 LocalRef<jobject> documentUri { getEnv()->CallStaticObjectMethod (DocumentsContract21,
865 DocumentsContract21.buildDocumentUriUsingTree,
866 javaUri.get(),
867 treeDocumentId.get()) };
868
869 return AndroidDocument { Utils::createPimplForSdk (documentUri) };
870 #else
871 return AndroidDocument{};
872 #endif
873}
874
876 : AndroidDocument (other.pimpl != nullptr ? other.pimpl->clone() : nullptr) {}
877
879 : pimpl (std::move (pimplIn)) {}
880
881AndroidDocument::AndroidDocument (AndroidDocument&&) noexcept = default;
882
883AndroidDocument& AndroidDocument::operator= (const AndroidDocument& other)
884{
885 AndroidDocument { other }.swap (*this);
886 return *this;
887}
888
889AndroidDocument& AndroidDocument::operator= (AndroidDocument&&) noexcept = default;
890
891AndroidDocument::~AndroidDocument() = default;
892
893bool AndroidDocument::deleteDocument() const { return pimpl->deleteDocument(); }
894
896{
897 jassert (hasValue());
898
899 auto renamed = pimpl->renameTo (newDisplayName);
900
901 if (renamed == nullptr)
902 return false;
903
904 pimpl = std::move (renamed);
905 return true;
906}
907
909{
910 jassert (hasValue() && target.hasValue());
911 return AndroidDocument { pimpl->copyDocumentToParentDocument (*target.pimpl) };
912}
913
915 const String& name) const
916{
917 jassert (hasValue());
918 return AndroidDocument { pimpl->createChildDocumentWithTypeAndName (type, name) };
919}
920
922{
923 return createChildDocumentWithTypeAndName (AndroidDocumentDetail::dirMime, name);
924}
925
928{
929 jassert (hasValue() && currentParent.hasValue() && newParent.hasValue());
930 auto moved = pimpl->moveDocumentFromParentToParent (*currentParent.pimpl, *newParent.pimpl);
931
932 if (moved == nullptr)
933 return false;
934
935 pimpl = std::move (moved);
936 return true;
937}
938
940{
941 jassert (hasValue());
942 return pimpl->createInputStream();
943}
944
946{
947 jassert (hasValue());
948 return pimpl->createOutputStream();
949}
950
952{
953 jassert (hasValue());
954 return pimpl->getUrl();
955}
956
958{
959 jassert (hasValue());
960 return pimpl->getInfo();
961}
962
964{
965 return getUrl() == other.getUrl();
966}
967
969{
970 return ! operator== (other);
971}
972
973AndroidDocument::NativeInfo AndroidDocument::getNativeInfo() const
974{
975 jassert (hasValue());
976 return pimpl->getNativeInfo();
977}
978
979//==============================================================================
981{
982 virtual ~Pimpl() = default;
983 virtual AndroidDocument read() const = 0;
984 virtual bool increment() = 0;
985};
986
988{
990
991 ~Utils() = delete; // This struct is a single-file namespace
992
993 template <typename Engine>
994 struct TemplatePimpl final : public Pimpl, public Engine
995 {
996 template <typename... Args>
997 TemplatePimpl (Args&&... args) : Engine (std::forward<Args> (args)...) {}
998
999 AndroidDocument read() const override { return Engine::read(); }
1000 bool increment() override { return Engine::increment(); }
1001 };
1002
1003 template <typename Engine, typename... Args>
1004 static AndroidDocumentIterator makeWithEngineInplace (Args&&... args)
1005 {
1006 return AndroidDocumentIterator { std::make_unique<TemplatePimpl<Engine>> (std::forward<Args> (args)...) };
1007 }
1008
1009 template <typename Engine>
1010 static AndroidDocumentIterator makeWithEngine (Engine engine)
1011 {
1012 return AndroidDocumentIterator { std::make_unique<TemplatePimpl<Engine>> (std::move (engine)) };
1013 }
1014
1015 static void increment (AndroidDocumentIterator& it)
1016 {
1017 if (it.pimpl == nullptr || ! it.pimpl->increment())
1018 it.pimpl = nullptr;
1019 }
1020};
1021
1022//==============================================================================
1024{
1025 if (! dir.hasValue())
1026 return {};
1027
1028 using Detail = AndroidDocumentDetail;
1029
1030 #if JUCE_ANDROID
1031 if (21 <= getAndroidSDKVersion())
1032 {
1033 if (auto uri = dir.getNativeInfo().uri)
1034 return Utils::makeWithEngine (Detail::makeDocumentsContractIteratorEngine (uri));
1035 }
1036 #endif
1037
1038 return Utils::makeWithEngineInplace<Detail::DirectoryIteratorEngine> (dir.getUrl().getLocalFile(), false);
1039}
1040
1042{
1043 if (! dir.hasValue())
1044 return {};
1045
1046 using Detail = AndroidDocumentDetail;
1047
1048 #if JUCE_ANDROID
1049 if (21 <= getAndroidSDKVersion())
1050 {
1051 if (auto uri = dir.getNativeInfo().uri)
1052 return Utils::makeWithEngine (Detail::RecursiveEngine { uri });
1053 }
1054 #endif
1055
1056 return Utils::makeWithEngineInplace<Detail::DirectoryIteratorEngine> (dir.getUrl().getLocalFile(), true);
1057}
1058
1060 : pimpl (std::move (engine))
1061{
1062 Utils::increment (*this);
1063}
1064
1066
1068{
1069 Utils::increment (*this);
1070 return *this;
1071}
1072
1073} // namespace juce
T any_of(T... args)
Some information about a document.
bool isDirectory() const
True if this is a directory rather than a file.
An iterator that visits child documents in a directory.
AndroidDocument operator*() const
Returns the document to which this iterator points.
static AndroidDocumentIterator makeRecursive(const AndroidDocument &)
Create an iterator that will visit each item in this directory, and all nested directories.
AndroidDocumentIterator & operator++()
Moves this iterator to the next position.
static AndroidDocumentIterator makeNonRecursive(const AndroidDocument &)
Create an iterator that will visit each item in this directory.
AndroidDocumentIterator()=default
Creates an end/sentinel iterator.
Represents a permission granted to an application to read and/or write to a particular document or tr...
static void takePersistentReadWriteAccess(const URL &)
Gives your app access to a particular document or tree, even after the device is rebooted.
static void releasePersistentReadWriteAccess(const URL &)
Revokes persistent access to a document or tree.
static std::vector< AndroidDocumentPermission > getPersistedPermissions()
Returns all of the permissions that have previously been granted to the app, via takePersistentReadWr...
Provides access to a document on Android devices.
static AndroidDocument fromFile(const File &filePath)
Create an AndroidDocument representing a file or directory at a particular path.
bool moveDocumentFromParentToParent(const AndroidDocument &currentParent, const AndroidDocument &newParent)
Experimental: Attempts to move this document from one parent to another, and returns true on success.
AndroidDocument()
Create a null document.
AndroidDocumentInfo getInfo() const
Fetches information about this document.
bool operator==(const AndroidDocument &) const
True if the URLs of the two documents match.
std::unique_ptr< InputStream > createInputStream() const
Creates a stream for reading from this document.
static AndroidDocument fromTree(const URL &treeUrl)
Create an AndroidDocument representing the root of a tree of files.
AndroidDocument createChildDirectory(const String &name) const
Attempts to create a new nested directory with a particular name.
AndroidDocument copyDocumentToParentDocument(const AndroidDocument &target) const
Experimental: Attempts to copy this document to a new parent, and returns an AndroidDocument represen...
bool renameTo(const String &newDisplayName)
Renames the document, and returns true on success.
bool deleteDocument() const
Attempts to delete this document, and returns true on success.
bool hasValue() const
True if this object actually refers to a document.
AndroidDocument createChildDocumentWithTypeAndName(const String &type, const String &name) const
Attempts to create a new nested document with a particular type and name.
static AndroidDocument fromDocument(const URL &documentUrl)
Create an AndroidDocument representing a single document.
URL getUrl() const
Returns the content URL describing this document.
bool operator!=(const AndroidDocument &) const
False if the URLs of the two documents match.
std::unique_ptr< OutputStream > createOutputStream() const
Creates a stream for writing to this document.
Represents a local file or directory.
Definition juce_File.h:45
bool isDirectory() const
Checks whether the file is a directory that exists.
bool hasWriteAccess() const
Checks whether a file can be created or written to.
File getChildFile(StringRef relativeOrAbsolutePath) const
Returns a file that represents a relative (or absolute) sub-path of the current one.
bool hasReadAccess() const
Checks whether a file can be read.
The JUCE String class!
Definition juce_String.h:53
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
Represents a URL and has a bunch of useful functions to manipulate it.
Definition juce_URL.h:38
File getLocalFile() const
Returns the file path of the local file to which this URL refers to.
Definition juce_URL.cpp:387
Represents a shared variant value.
Definition juce_Value.h:51
#define jassert(expression)
Platform-independent assertion macro.
#define jassertfalse
This will always cause an assertion failure.
JUCE Namespace.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
long long int64
A platform-independent 64-bit integer type.
read
T push_back(T... args)