How to convert file path to treeUri - java

In my application i have a regular Settings, where user can choose a video directory on his android device, and videos found in that directory will play at some point in the application. Now, when user himself is doing that via Preferences, i am opening ACTION_OPEN_DOCUMENT_TREE, and Uri returned is treeUri, that i am later using in my Video Activity with DocumentsContract.buildChildDocumentsUriUsingTree. This part is completely fine, it's working and all is OK.
However, since this application is going to be used in enterprises, i have a request, that IT department can submit an external xml file that i am completely controlling in application external directory, with all options that you as a user can set in the application itself, and if that file exists, those options need to be implemented in SharedPreferences. I got all settings to be implemented from that external xml file, apart that video directory. I was thinking that user can give regular path in the xml, like <setting key="video_folder">/sdcard/Movies</setting>, and then i parse that in the application, get treeUri from path, and save it in SharedPreferences for later use. Any ideas if this is doable, or this should have some other approach?
I've tried this method below:
public static Uri[] getSafUris (Context context, File file) {
Uri[] uri = new Uri[2];
String scheme = "content";
String authority = "com.android.externalstorage.documents";
// Separate each element of the File path
// File format: "/storage/XXXX-XXXX/sub-folder1/sub-folder2..../filename"
// (XXXX-XXXX is external removable number
String[] ele = file.getPath().split(File.separator);
// ele[0] = not used (empty)
// ele[1] = not used (storage name)
// ele[2] = storage number
// ele[3 to (n-1)] = folders
// ele[n] = file name
// Construct folders strings using SAF format
StringBuilder folders = new StringBuilder();
if (ele.length > 4) {
folders.append(ele[3]);
for (int i = 4; i < ele.length - 1; ++i) folders.append("%2F").append(ele[i]);
}
String common = ele[2] + "%3A" + folders.toString();
// Construct TREE Uri
Uri.Builder builder = new Uri.Builder();
builder.scheme(scheme);
builder.authority(authority);
builder.encodedPath("/tree/" + common);
uri[0] = builder.build();
// Construct DOCUMENT Uri
builder = new Uri.Builder();
builder.scheme(scheme);
builder.authority(authority);
if (ele.length > 4) common = common + "%2F";
builder.encodedPath("/document/" + common + file.getName());
uri[1] = builder.build();
return uri;
}
but that gives me:
Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/Movies%3A/document/Movies%3A/children from pid=28318, uid=10157 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

I had to completely rearrange logic for playing videos, to read them from regular file:// uri's, in order to bypass SAF. In short, my question was, if you don't obtain tree URI from ACTION_OPEN_DOCUMENT, ACTION_OPEN_DOCUMENT_TREE, and similar API's, is there any way possible, to obtain a permission for such tree URI. As far as i could tell, that is not possible (not even with any kind of runtime permission presented to the user). If anyone else believes that there is such a possibility, please, let me know.

Related

How do I reveal a MS Graph (search result) driveItem File's path (folder)?

I have following (kotlin/java based) query in MSGraph
var driveItemSearchCollectionRequestBuilder =
safeGraphServiceClient
.sites(SHAREPOINT_SITE_ID)
.drive()
.root()
.search("¤A=118628")
do{
driveItemSearchCollectionPage = driveItemSearchCollectionRequestBuilder?.buildRequest()?.get()?:break
driveItemSearchCollectionPage.currentPage.map {driveItem->
driveItem?.let{ safeDriveItem ->
//Here I need to find my `safeDriveItem`'s (which is a file) path (where the file is stored)... (or folder)
//`safeDriveItem.folder` is null... (since this is a file)
}
}
driveItemSearchCollectionRequestBuilder = driveItemCollectionPage.nextPage
}while(driveItemSearchCollectionRequestBuilder!=null)
which results in a set (page) of driveItems. This search can find the file in any folder in my sharepoint tree. Where (or how) can I find the drivItem file's folder (i.e. '\MyFolder\Level1\Level2\Level3')? (The folder item is null for driveItem here, and I haven't found any value which contains it). Or do I need to do som "clever" backtracking?
When ever you search using the above code, as you said you will be getting the driveItems. Pick the id of the driveItem which you want the Folder path for and then call
https://graph.microsoft.com/v1.0/sites/{siteid}/drive/Items/{driveItemid}
which will pull the whole drive item object which has a parentReferrence object which internally have path property in it.
Sharepoint has two different data sources where search will pull the data by indexing from one source and few properties may not showup. So pulling an object directly gives you all the properties.
#Shiva found a solution, and I can now query the path for my file like this:
var driveItemSearchCollectionRequestBuilder =
safeGraphServiceClient
.sites(SHAREPOINT_SITE_ID)
.drive()
.root()
.search("¤A=118628")
do{
driveItemSearchCollectionPage = driveItemSearchCollectionRequestBuilder?.buildRequest()?.get()?:break
driveItemSearchCollectionPage.currentPage.map {driveItem->
driveItem?.let{ safeDriveItem ->
val pathItem = safeGraphServiceClient
.sites(SHAREPOINT_SITE_ID)
.drive()
.items(safeDriveItem.id)
.buildRequest()
.get()
val path = pathItem.parentReference.path
galleryItems.add(path, driveItem.name) //My functiomn adds now path and file to db
}
}
driveItemSearchCollectionRequestBuilder = driveItemCollectionPage.nextPage
}while(driveItemSearchCollectionRequestBuilder!=null)
I hope in future the driveItem.parentReference.path could be populated so we can avoid a secondary call to Graph, or there is some switch to set on the search phrase to disclose the path (communication cost perspective).

Android Share Intents - Some files won't share

I am having some issues with Android share intents. I am able to succesfully share image files (jpg, png, gifs), but when I try to share any other files (doc, docx, xlsx, ppt), I get errors from the apps saying that there were errors opening the files, but when I try to open them from the file manager, they work fine.
var uri = Android.Net.Uri.Parse(System.IO.Path.Combine(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryDownloads).AbsolutePath, fileName));
string auth = "xamarintestapp.xamarintestapp.fileprovider";
string mimeType = Android.Webkit.MimeTypeMap.Singleton.GetMimeTypeFromExtension(Android.Webkit.MimeTypeMap.GetFileExtensionFromUrl(fileName.ToLower()));
if (mimeType == null)
mimeType = "*/*";
var file = new Java.IO.File(System.IO.Path.Combine(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryDownloads).AbsolutePath, fileName));
Android.Net.Uri intentUri = null;
Intent intent = new Intent(Intent.ActionView);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
Forms.Context.StartActivity(Intent.CreateChooser(intent, "Choose an App"));
I have tried checking the MIME type, and they seem to be correct (application/vnd.openxmlformats-officedocument.wordprocessingml.document for doc and docx files). Any help would be greatly appreciated.
You need to use a file scheme-based based uri instead of just passing a filesystem-based path.
Note: The Downloads directory is a publicly accessible file location on Android so no granting of rights, nor content provider, is needed, but if these files, doc|x or not, are coming from within your app's sandbox, then you would need to implement a content provider and share a content://-based provider uri to Word, Excel and other apps...
Example:
var fileName = "demo.docx";
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(fileName)) ?? "*/*";
var downloads = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryDownloads);
using (var intent = new Intent(Intent.ActionView))
using (var uri = new Uri.Builder()
.Scheme("file")
.Authority("localhost")
.AppendEncodedPath(downloads.CanonicalPath)
.AppendEncodedPath(fileName)
.Build())
{
intent.SetDataAndType(uri, mimeType);
StartActivity(Intent.CreateChooser(intent, "Choose an App"));
}
Update:
....exposed beyond app through ClipData.Item.getUri()
Compiling against, say Android P/API-28, and using a minSDKVersion but no targetSDKVersion in the manifest (Xamarin calls it "automatic") and this code will work (I have Android P apps using the latest APIs running using the above code but they do not "target" a specific API level at runtime.)
But you are targeting a specific API >= Nougat thus you will have to implement a file provider to "share" even public files and thus provide content://-based uris to the app you are sharing to.

Amazon Kindle Fire HD Saving Files onto MicroSD Cards

Before It gets brought up about my question already being asked, I would like to state that I have tried around 5 other options and possible solutions with no result.
Here is a snippet of my code. This is just a snippet. Upon testing the results of my code currently, a file is being saved in the main directory, /ScoutingApp. However, I would like to files to save in a folder /ScoutingApp/ on the MicroSD card so I can eject data more quickly.
if (Environment.MEDIA_MOUNTED.equals(state)) {
File root = Environment.getExternalStorageDirectory();
File Dir = new File(root.getAbsolutePath() + "/ScoutingApp");
if (!Dir.exists()) {
Dir.mkdir();
} else {
filename = UUID.randomUUID().toString() + ".sql";
File file = new File(Dir, filename);
If the Android that your Fire OS is based on is Android 4.4+, you can try getExternalFilesDirs() on any Context (such as an Activity). Note the plural form — if this method returns 2+ items, the second and subsequent ones are on removable storage. Those locations will be specific for your app, and you can read from and write to those locations without permissions.
Note, though, that Fire OS is not completely compliant with the Play ecosystem's compatibility requirements, and so YMMV.

Cannot find protocol 'resource' in URL constructor

I've implemented a class to read from RSS 2.0 and Atom 1.0 feeds. I want to write some unit tests in order to verify functionality. Here is the feed reader section of my code:
private String readFeed(final String url) throws IOException
{
final StringBuilder builder = new StringBuilder();
final URL feedUrl = new URL(url);
final BufferedReader in = new BufferedReader(
new InputStreamReader(feedUrl.openStream()));
String input;
while ((input = in.readLine()) != null)
{
builder.append(input);
}
in.close();
return builder.toString();
}
After some research, I figured the best way to test would be to have a sample feed as an XML file in my project resources directory.
I've created a example file "resources/rss2-0.xml"
I'm sending in the following value to the readFeed function, "resource:///rss2-0.xml",
and I keep receiving java.net.MalformedURLException: unknown protocol: resource
This is my first time using a URL pathway to load from a resource. From what I can tell, resource seems like it should be a valid protocol. Anyone have any ideas what I may be doing wrong or other ways to go about this?
If you want to deal with path using your local file system, the Path class is best suited for this task.
An object that may be used to locate a file in a file system. It will
typically represent a system dependent file path.
You can use it like so :
Path path = FileSystems.getDefault().getPath("/resources/rss2-0.xml");
BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);
If your really want to deal with URL, the protocol you're looking for is simply "file". So it would be file:///rss2-0.xml instead of resource:///rss2-0.xml and even file:/resources/rss2-0.xml to be exact.
Note that in your case, you will indeed have to deal with URLs sooner or later, but when working on local tests, using the Path class will save you troubles. If you want another alternative, try the URI class. Since an URI is an identifier (see difference between URI and URL) it can identify either an URL or a Path an may serve as a bridge between your production code which will ultimately deal with URLs and your test code where the Path class could be best put in use.
For example :
public interface FeedReader {
String readFeed(final URI uri);
}
And 2 implementations, one for testing locally :
public class LocalFeedReader implements FeedReader {
#Override
public String readFeed(final URI uri) {
// URI -> Path
// then dealing with Path to target local rss2-0.xml file
}
}
And one for production code :
public class WebFeedReader implements FeedReader {
#Override
public String readFeed(final URI uri) {
// URI -> URL
// then dealing with URL to target real resources
}
}
The java docs say that only http, https, file, and jar are "guaranteed" to exist on the search path for protocol handlers. Others only "may" be supported.
http://docs.oracle.com/javase/8/docs/api/java/net/URL.html#URL-java.lang.String-java.lang.String-int-java.lang.String-
It looks like if you want a custom handler that isn't supported in your java distribution, you'll have to create one.
http://mjremijan.blogspot.com/2012/02/create-your-own-java-url-handlers.html

Deleting files via a 'ContentResolver' as opposed to deleting them via 'file.delete()'

I have just written a function in an android app that deletes a file using the standard 'File' class in Java. i.e:
String fileName= "/mnt/Gallery/Img001.jpg";
File file = new File(fileName);
file.delete();
While the above procedure is simple enough, I have been wondering if there is any advantage to doing the same via a 'ContentResolver'. Any advice would be appreciated.
------------------------------------------ EDIT ----------------------------------------
Here's an example of deleting a file via the Content Resolver. This example assumes the file being deleted is an image and that its 'id' is known.
long mediaId = 155; // NOTE: You would normally obtain this from the content provider!
Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri itemUri = ContentUris.withAppendedId(contentUri, mediaId);
int rows = getContentResolver().delete(itemUri, null, null);
String path = itemUri.getEncodedPath();
if(rows == 0)
{
Log.e("Example Code:","Could not delete "+path+" :(");
}
else
{
Log.d("Example Code:","Deleted "+path+ " ^_^");
}
Android's content provider framework has certain added advantages when compared to directly manipulating data.
You can think on the lines of 'Where does the file reside and who may be deleting it'.
Scenario 1
File resides on SD card (a path accessible by your app) and you app is deleting it.
Solution : Since the path is accessible to you, the java approach will work with a file Uri like:
file://mnt/sdcard/downloads/image.jpeg
Scenario 2
File resides in another app (say dropbox) and your app needs to delete the file.
Solution : This means that the file actually resides in the private storage of another app. A file: Uri will the above approach will give you access denied. So, your app will need to fetch a content Uri from the app containing the file and call into its content provider to delete.
fileUri = Uri.parse ("content : // " + packageContainingTheFile " + fileId); // replace this with Uri obtained from the app.
getContext().getContentResolver().delete (fileUri, null, null);
Scenario 3
File resides in your app's package directory i.e, under data/data/com.yourpackage/yourfolder/yourfile.xxx and your app is the only one deleting it.
Solution : Here, either of the above approaches will work since you have the access to delete the file.
Uri will look like:
file://data/data/yourpackage/folder/file.ext
The prime advantage of using content provider here is that you automatically gain the observer model. Content provider callbacks are a well defined entry point from where data is modified. Hence, its a desired place to notify others of changes using:
getContext().getContentResolver().notify(uri, null)
Assume you have views that show a listing of such file items. As soon as the delete is done, your can be notified.
Scenario 4
File resides in your app's package directory i.e, under data/data/com.yourpackage/yourfolder/yourfile.xxx and you want to expose the delete functionality to other apps.
Solution : This is similar to Scenario 1, just the other way round. Other apps cannot delete the file in your private storage with a Uri like
file://data/data/yourpackage/folder/file.ext // works just for your app
They will need to call in your content provider to do this with a Uri like.
content://providerAuthority/delete/id
which your content provider will need to map to file.ext absolute path.
Summary
To conclude, the use of content provider is necessary is some scenarios while optional in others. It largely depends on your app requirements. If you have views, CursorLoaders in place and want to be informed about updates or wish to expose deletion of your app data to other apps, content provider is the cleanest approach.

Categories