i searched a lot about this topic
but i still can't wrap my brain around it
can someone "deeply explains to me " how getType() works ? and few examples that also explains the mime and how it is used when it is returned by getType() ?
Content URIs can be used to reference content from a wide variety of contexts. The getType method allows a content consumer - which might be a component in your app, but can also be component outside your app and which doesn't know anything specifically about your app except for the content URI - to find out what type of data a content URI refers to. The content consumer needs this information to know how what to do with the content - for example, how to display it - when it resolves the content URI. So in the simplest case, if the URI content://my.app/record/1 refers to a HTML file, then the type is text/html, and if the URI content://my.app/record/2 refers to a JPEG file then the type is image/jpeg. There is no other way to infer the content type from the URI (because e.g. there's no file extensions).
Android also provides some special MIME types for indicating table data, android.cursor.item/* and android.cursor.dir/*.
This approach is designed to fit in with Android's Activity based architecture, allowing the system to open content URIs by examining the content MIME type and then choosing an Intent to open an Activity which displays the content.
Related
Setup: I get an URI of the directory in shared storage using procedure described here: Access documents and other files from shared storage / Grant access to a directory's contents.
Question: How to create a file inside this directory?
More details:
The guide mentioned above explains, how to list files in this directory, open some file for reading or modification and also how to delete it. All this using ContentResolver. (Edit: actually guide does not explain it also, but just how to delete or modify files obtained interactively with ACTION_OPEN_DOCUMENT). But I cannot see, how to create some file inside this directory.
Two candidates, which I can imagine, could help me with the job are: ContentResolver.insert() or ParcelFileDescriptor(file, MODE_CREATE), but I cannot figure out how I could use them in this situation.
I should also mention, that I know about the possibility to create the file, when user interactively selects the file name and location, but it is not good solution for me. In my context I need to create several files inside one folder. All files should be accessible/modifiable/deletable by user from outside of my application, thus "shared storage". And the files are named and located within their "main" directory according to some conventions, so I don't want the user to explicitly provide me the name for each file, but just the location of "main directory".
Point 1: blackapps is right in the comments to the original question and this question is duplicate of e.g. those:
Create new file in the directory returned by Intent.ACTION_OPEN_DOCUMENT_TREE
or
Write file to directory using Intent.ACTION_OPEN_DOCUMENT_TREE
Point 2 (Quick answer): One should use in this case DocumentFile.fromTreeUri(this, uri) with the URI returned from the Intent.ACTION_OPEN_DOCUMENT_TREE. This worked for me out of the box.
Point 3: One thing was still confusing for me:
If I tried to use something like this (from the original guide, which I used) - to query metadata of the URI:
Cursor cursor = getContentResolver().query(uri, null, null, null, null, null);
Or if I tried to call something like this, hoping to create new document without use of DocumentFile, but instead using directly DocumentsContract:
Uri createdDocumentUri = DocumentsContract.createDocument(
getContentResolver(),
uri,
"text/plain",
"someDocument.txt");
Then I always got an exception. In the first case it was:
java.lang.UnsupportedOperationException: Unsupported Uri content://com.android.externalstorage.documents...
And in the second case:
java.lang.IllegalArgumentException: Invalid URI: content://com.android.externalstorage.documents...
Different exceptions, but in both cases the message is "Unupported URI".
For a while I could not understand, why is it the case. But when I looked inside DocumentFile.fromTreeUri() and discovered, that instead of using URI returned by Intent.ACTION_OPEN_DOCUMENT_TREE directly, one needs to process it in a way like this (the code is taken from DocumentFile.fromTreeUri()):
String documentId = DocumentsContract.getTreeDocumentId(uri);
Uri newUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId);
The documentation of DocumentsContract.buildDocumentUriUsingTree(), explains little bit, what is going on:
... However, instead of directly accessing the target document, the
returned URI will leverage access granted through a subtree URI,
typically returned by Intent#ACTION_OPEN_DOCUMENT_TREE. The target
document must be a descendant (child, grandchild, etc) of the subtree.
This is typically used to access documents under a user-selected
directory tree, since it doesn't require the user to separately
confirm each new document access.
I hope, that this answer will help someone to save a bit of confusion, which I had, while I was trying to clarify this topic...
I'm using content URI's to link content (more like launch a specific activity) in an app and other apps that I've developed but the problem is anytime one application is selected from the android launcher the rest of the URI's keep opening using that singular app
I'm building the uri links using the Linkify class. Below shows the URI's
Pattern inlinkMatcher = Pattern.compile("\\(click[^()]*\\)|\\(you can[^()]*\\)|\\(check here [^()]*\\)");
String inLinkURL = "content://com.n4labs.sexed.providers/hgcontent/";
Pattern inlinkMatcher2 = Pattern.compile("\\(click here to find [^()]*\\)");
Pattern inlinkMatcher3 = Pattern.compile("\\(learn more [^()]*\\)|\\(talk to [^()]*\\)");
boolean yfsinstalled = appInstalledOrNot("com.n4labs.yfs");
String inLinkURL2 = "http://market.android.com/details/?id=com.n4labs.yfs";
if(yfsinstalled)
inLinkURL2 = "content://com.n4labs.yfs.providers/centersearch/";
boolean divainstalled = appInstalledOrNot("com.n4labs.diva");
String inLinkURL3 = "http://market.android.com/details/?id=com.n4labs.diva";
if(divainstalled)
inLinkURL3 = "content://com.n4labs.diva.providers/learn/";
And the calls to Linkify
if(yfsinstalled){
Linkify.addLinks(itemController3.paragraphtext, inlinkMatcher2, inLinkURL2);
}
else
{
Linkify.addLinks(itemController3.paragraphtext, inlinkMatcher2, inLinkURL2, null, mentionFilter);
}
if(divainstalled){
Linkify.addLinks(itemController3.paragraphtext, inlinkMatcher3, inLinkURL3);
}
else
{
Linkify.addLinks(itemController3.paragraphtext, inlinkMatcher3, inLinkURL3, null, mentionFilter);
}
Linkify.addLinks(itemController3.paragraphtext, inlinkMatcher, inLinkURL);
The provider in each app has the appropriate authority and is exported as such
<provider
android:name="com.n4labs.diva.providers.HealthGuideContentProvider"
android:authorities="com.n4labs.diva.providers"
android:exported="true">
</provider>
How can I ensure that each URI opens in the appropriate application automatically, or at least the option is displayed to the user every-time.
I hope I was clear enough.
Anyone?
Thanks.
Each of your ContentProviders is reporting that the MIME type associated with those Uri values is text/plain. This is a very common MIME type, one normally associated with standard text files.
When the user clicks on a link for any of those Uri values, Android will construct an ACTION_VIEW Intent, for a MIME type of text/plain, and attempt to start an activity for that, such as a text editor.
The key now is: what do these ContentProviders actually deliver, as content, for those Uri values? In other words, if I were to call openInputStream() on a ContentResolver, passing in one of those Uri values, what data do I get back in the stream?
There are five main possibilities that I see:
They legitimately return plain text, and you really do want an ordinary text editor to be an option for the user to work with that text. In that case, your setup is fine. Bear in mind that the user might elect to click the "always" option for handling these Uri values and therefore may not necessarily want to be presented with a choice of activities each time. After all, in this scenario, all three of your activities can work with all three of your providers, and regular text editors can also work with these providers.
They legitimately return plain text, but you really do not want anything other than your activities handling those Uri values. In that case, get rid of the ContentProviders, get rid of Linkify, and add your own ClickableSpans to the text to directly start activities of your choosing.
They do not return plain text, but instead return data in some other format, and you are willing for third-party apps to be able to work with that content. In that case, change the MIME type (in the provider and in the associated <intent-filter>) to the correct value, instead of text/plain. This may involve you creating your own custom "vendor" MIME type, if your data does not match any standard data format.
The openInputStream() call would crash, because you have a buggy ContentProvider that is not actually serving data for these Uri values. In that case, fix the ContentProvider, then run through this list of possibilities again. Since you are exporting this provider, you need to actually implement it properly.
The openInputStream() call would crash, or the providers would return something other than plain text, but you are not intending on anybody actually using this content other than yourself. In that case, get rid of the ContentProvider, get rid of Linkify, and add your own ClickableSpans to the text to directly start activities of your choosing.
My guess is that your case is #2 or #5.
I need to make all numbers in a string become links.
The expected action when any of these links is clicked is to append the clicked number to an existing string.
I managed to linkify the numbers by using the following code:
Pattern myMatcher = Pattern.compile("[0-9]*");
Linkify.addLinks(myString, myMatcher, null);
How can I access and retrieve the clicked number in this case?
I tried looking in other questions related to Linkify but seems all are describing ways to have an action that opens an activity or open the default app for that link type (email address/web URL/etc.)
Thanks in advance for you help :)
You can Customize Linkify to append any predefiened string(scheme) into that.
Take a look at the following post Android Developer Blogspot (Search for "Custom Linkify")
For clarity I am describing a portion of that post here:
Linkify will automatically append whatever is matched to a scheme that
is supplied to it, so for the sake of argument let's assume we have a
ContentProvider that matches the following content URI:
content://com.google.android.wikinotes.db.wikinotes/wikinotes/WikiWord
The WikiWord part will be appended by Linkify when it finds a match,
so we just need the part before that as our scheme.
Now that we have these two things, we use Linkify to connect them up:
Pattern wikiWordMatcher = Pattern.compile("\\b[A-Z]+[a-z0-9]+[A-Z][A-Za-z0-9]+\\b");
String wikiViewURL = "content://com.google.android.wikinotes.db.wikinotes/wikinotes/";
Linkify.addLinks(noteView, wikiWordMatcher, wikiViewURL);
Linkify can be used multiple times on the same view to add more links,
so using this after the Default Linkify call means that the existing
active links will be maintained and the new WikiWords will be added.
You could define more Linkify actions and keep applying them to the
same TextView if you wanted to.
Now, if we have a WikiWord in the TextView, let's say MyToDoList,
Linkify will turn it into an active link with the content URI:
content://com.google.android.wikinotes.db.wikinotes/wikinotes/MyToDoList
and if you click on it, Android will fire the default intent for that
content URI.
For this to all work, you will need a ContentProvider that understands
that Content URI, and you will need a default activity capable of
doing something with the resulting data. I plan to cover these in
future blog entries (and soon). In fact, the whole Wiki Note Pad
application is currently undergoing some clean up and review, and will
then hopefully be released as a sample application.
I have a working custom search suggestions class (via http://developer.android.com/guide/topics/search/adding-custom-suggestions.html). It currently returns one type of information - "product names".
I've added some additional activities (screens) to my app so that if a person is on a product page, starting up a search should return results from "product names", but if they are in another activity, I would like the search suggestions to pull "manufacturer names".
I saw Accessing appSearchData bundle from ContentProvider, but the custom search suggestions provider extends ContentProvider which doesn't work with the answer
Bundle b = intent.getBundleExtra(SearchManager.APP_DATA);
This Bundle is available to the search results class, but not the contentprovider.
How best to pass a parameter ("product" or "manufacturer") to a search suggestions content provider?
This doesn't seem like an ideal solution, but I had the same need and I found I could get the job done by adding a public ivar or method to the subclass of ContentProvider that handles the search suggestions. Prior to initiating the search, you can configure your provider as needed. You can access the provider instance from an activity like so:
ContentProviderClient client = getContentResolver().acquireContentProviderClient("my.searchsuggestionprovider");
MyProviderClass provider = (MyProviderClass) client.getLocalContentProvider();
Now you can configure with provider.setParameter("product") or whatever you need. You might need to reset the parameter to a default or something after building your cursor.
Edit: This turned out to be impractical, at least in my case, since the content provider is called with query() every time a character is typed. Instead, I have employed a workaround similar to what is described in set-search-hint-dynamically. By creating an alternate "searchable" XML definition and activity, you can alter the URI that's passed to the content provider in query(), adding a path component to provide the additional parameter or context you need.
I just made a static variable for the parameter on my content provider, and set it from the activity. I been thinking about it, and it's the cleanest workaround I have found!
Looking at content providers, I'm not quite clean on the typical usage of the getType() method. The API doc says about implementing this method that
This allows [applications] to retrieve the MIME
type for a URI when dispatching
intents.
Could anyone describe a typical case where using it would be particularly useful?
For example, you're writing content provider for picture gallery. You should mention in your getType() method that you provide pictures - jpg or png. So, when one will launch image gallery, it will be able to show built-in pictures and pictures provided by your content provider.
In pseudocode the user of contentProvider do something like:
List contentProviders = getProviders();
List resultProviders;
final Type type = Type.JPG;
for (ContentProvider provider : contentProviders) {
if (type == provider.getType()) {
resultProviders.add(provider);
}
}
This is pseudocode, but I hope you will got the idea.
As I understand it, a use case could be the following:
App A contains the content provider. App B uses that content provider to retrieve all the data items from App A. The user then picks one of these (in App B) and after that an activity in App A to show/edit/delete the selected data item should be started. So App B then creates an intent, and to make sure that an activity in App A handles it, you need to set the (mime-)type of the intent to the mime-type of the uri (the show/edit/delete activities in App A has added this mime type to their intent filters).