I have a list of images:
private int[] images = {
R.drawable.blue_icon_left_foot,
R.drawable.blue_icon_right_foot,
R.drawable.blue_icon_left_hand,
R.drawable.blue_icon_right_hand,
R.drawable.green_icon_left_foot,
R.drawable.green_icon_right_foot,
R.drawable.green_icon_left_hand,
R.drawable.green_icon_right_hand,
R.drawable.red_icon_left_foot,
R.drawable.red_icon_right_foot,
R.drawable.red_icon_left_hand,
R.drawable.red_icon_right_hand,
R.drawable.yellow_icon_left_foot,
R.drawable.yellow_icon_right_foot,
R.drawable.yellow_icon_left_hand,
R.drawable.yellow_icon_right_hand};
I want to let the user pick an image from the gallery by using an Intent:
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Select Picture"), 1);
One of the images of the array should then be replaced with the new image given by the user. When I get the image from the user, I only have a URI of the image:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == 1) {
Uri imageUri = data.getData();
//Insert image into images list
}
}
Is it possible to somehow get an integer ID of the image so that I can insert the image into the same list as the images from the drawable folder?
Or should I instead try to store a list of URI's (if it is possible to get URI's of the images from the drawable folder)?
Or is there a third solution that is completely different and much better?
You can generate ids with View.generateViewId() if you are using API 17 or larger.
From the main documentation:
Generate a value suitable for use in setId(int). This value will not
collide with ID values generated at build time by aapt for R.id.
Returns a generated ID value
You can check this answer to see what are the alternatives when you are using a lower API: Android: View.setID(int id) programmatically - how to avoid ID conflicts?
I think what you are trying to do is somewhat misguided. The resources in the /res directory are compile time resources that you bundle with your project. The image that a person selects via an intent from their device is a run time file that will vary by user, device, etc. You are better off not trying to treat them the same. Save the user selections as dataUris or strings and keep the resources as ints.
Related
This is my code for opening up the picker for Videos and Images - however I need to not show Videos that are longer than 5 minutes in length. Is this possible?
public void startChoosePhotoFromLibrary() {
if (checkOrRequestExternalStoreagePermission()) {
if (Build.VERSION.SDK_INT < 19) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/* video/*");
startActivityForResult(photoPickerIntent, PICK_PHOTO_ACTIVITY_REQUEST_CODE);
} else {
Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
photoPickerIntent.setType("*/*");
photoPickerIntent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
startActivityForResult(photoPickerIntent, PICK_PHOTO_ACTIVITY_REQUEST_CODE);
}
}
}
This is my code for opening up the picker for Videos and Images
ACTION_PICK does not use MIME types. ACTION_PICK picks from a collection of content, where that collection is identified by the Uri that you supply in the Intent.
Also, MIME types do not have spaces in them.
Is this possible?
Not via those Intent actions, or via any content-selection mechanism that is part of the Android SDK.
You are welcome to query the MediaStore for videos, and there may be a way to filter such videos by length. But then you would need to present your own UI for allowing the user to choose something from the query results (e.g., ListView, RecyclerView).
I want to create something like "PDF Viewer app". Application will search for all *.pdf files in location chosen by user. User can choose this folder by this function:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE);
Then I get DocumentFile (folder):
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == getActivity().RESULT_OK && requestCode == REQUEST_CODE) {
Uri uriTree = data.getData();
DocumentFile documentFile = DocumentFile.fromTreeUri(getActivity(), uriTree);
//rest of code here
}
}
Why I chose this method of selecting folder? Because I want to make possible to choose Secondary Storage (you know, in Android >= 5.0, you can't access Secondary Storage with Java.io.file).
Ok, so I get folder with all *.pdf as DocumentFile. Then I call:
for(DocumentFile file: documentFile.listFiles()){
String fileNameToDisplay = file.getName();
}
And this is VERY SLOW. It takes almost 30 seconds when there are ~600 files in chosen folder. To prove it, I chose directory from External Storage (not secondary storage), and then I tried two solutions: DocumentFile and File.
File version looks like it:
File f = new File(Environment.getExternalStorageDirectory()+"/pdffiles");
for(File file: f.listFiles()){
String fileNameToDisplay = file.getName();
}
}
Second version works about 500x faster. There is almost no time in displaying all files on List View.
Why is DocumentFile so slow?
If you read the source code of TreeDocumentFile, you will find that each call to listFiles() and getName() invokes ContentResolver#query() under the hood. Like CommonsWare said, this would perform hundreds of queries, which is very inefficient.
Here is the source code of listFiles():
#Override
public DocumentFile[] listFiles() {
final ContentResolver resolver = mContext.getContentResolver();
final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(mUri,
DocumentsContract.getDocumentId(mUri));
final ArrayList<Uri> results = new ArrayList<>();
Cursor c = null;
try {
c = resolver.query(childrenUri, new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
while (c.moveToNext()) {
final String documentId = c.getString(0);
final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(mUri,
documentId);
results.add(documentUri);
}
} catch (Exception e) {
Log.w(TAG, "Failed query: " + e);
} finally {
closeQuietly(c);
}
final Uri[] result = results.toArray(new Uri[results.size()]);
final DocumentFile[] resultFiles = new DocumentFile[result.length];
for (int i = 0; i < result.length; i++) {
resultFiles[i] = new TreeDocumentFile(this, mContext, result[i]);
}
return resultFiles;
}
In this function call, listFiles() made a query that only selects the document ID column. However, in your case you also want the file name for each file. Therefore, you can add the column COLUMN_DISPLAY_NAME to the query. This would retrieve the filename and document ID (which later you will convert it into Uri) in a single query and is much more efficient. There are also many other columns available such as file type, file size, and last modified time, which you may want to retrieve them as well.
c = resolver.query(mUri, new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
}, null, null, null);
Within the while loop, retrieve the filename by
final String filename = c.getString(1);
The above modified code is able to instantly retrieve the Uri and filename of a directory with 1000+ files.
In summary, my recommendation is to avoid using DocumentFile if you are working with more than just a few files. Instead use ContentResolver#query() to retrieve the Uri and other information by selecting multiple columns in the query. For file operations, use the static methods in the DocumentsContract class by passing the appropriate Uri's.
By the way, it seems that the sortOrder parameter of ContentResolver#query() gets completely ignored in the above code snippet when tested on Android 11 and Android 9. I would manually sort the results instead of relying on the query order.
Why is DocumentFile so slow?
For ~600 files you are performing ~600 requests of a ContentProvider to get the display name, which means ~600 IPC transactions.
Instead, use MediaStore to query for all indexed media with the application/pdf MIME type.
In case someone comes up here still looking for a solution,
I built a wrapper over this with some pretty good performance.
You can check the wrapper & performance info. here:
https://github.com/ItzNotABug/DocumentFileCompat
To use DocumentsContract to obtain children documents, see https://developer.android.com/reference/android/provider/DocumentsContract.html#buildChildDocumentsUriUsingTree(android.net.Uri, java.lang.String).
The Uri returned from ACTION_OPEN_DOCUMENT_TREE is a tree document URI. Use the above method to build the Uri to query all children documents.
The root document ID can be obtained using https://developer.android.com/reference/android/provider/DocumentsContract.html#getTreeDocumentId(android.net.Uri) with the Uri returned from ACTION_OPEN_TREE_DOCUMENT.
To obtain a speed little less than the one from File use DocumentsContract instead of DocumentFile to list the content of trees obtained with Intent.ACTION_OPEN_DOCUMENT_TREE.
I wanted to display my video thumbnail right after taking a video.
This is my current code in the onActivityResult method
if (requestCode == 101)
{
Bitmap bmThumbnail = ThumbnailUtils.createVideoThumbnail("/storage/emulated/0/myvideo.mp4", MediaStore.Images.Thumbnails.MICRO_KIND);
viewImage.setImageBitmap(bmThumbnail);
}
But it doesn't display anything on the image view.
Any workaround for this?
*UPDATE: FIXED
I've changed the code to:
if (requestCode == 101)
{
Bitmap bmThumbnail = ThumbnailUtils.createVideoThumbnail(f.getAbsolutePath().toString(), MediaStore.Images.Thumbnails.MINI_KIND);
viewImage.setImageBitmap(bmThumbnail);
}
The thumbnail is now showing and I have a bigger size, which is acceptable for viewing. Thanks to Sassa.
I just retrieved the absolute path of the file using f.GetAbsolutePath:
if (requestCode == 101)
{
Bitmap bmThumbnail = ThumbnailUtils.createVideoThumbnail(f.getAbsolutePath().toString(), MediaStore.Images.Thumbnails.MINI_KIND);
viewImage.setImageBitmap(bmThumbnail);
}
ThumbnailUtils.createVideoThumbnail will not work with all android OS hence you have to load ffmpeg library in your application to create thumbnail if you want to provide support for all android OS devices.
Thanks,
Devang
Trying to read the list of photos taken by the user and display them in the imageview. For the sake of simplicity, trying to just display one for now. Have this piece of code so far:
Cursor cc = this.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null,null);
which gets me some data in the cursor, cc.getCount() appears to be making sense (goes up by one when I take a picture, etc.). However, I cannot display the contents in the imageView at all, nothing ever shows up.
Iv'e tried this (s_id is the id of the pic from the cursor above, first returned column):
Uri u;
u = Uri.withAppendedPath(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, "" + s_id);
im.setImageURI(u);
Also tried this:
Bitmap b = BitmapFactory.decodeFile(u.getPath());
im.setImageBitmap(b);
No worky. Help?
ps. no errors show up anywhere.
Here's another way you can let the user pick an image and display it in an imageview.
This code will allow the user to look through files to pick an image from the gallery:
Intent picture = new Intent(Intent.ACTION_GET_CONTENT);
picture.setType("image/*");
startActivityForResult(picture, YOUR_CODE);
Then in the onActivityResult method you can use the data to set the imageview:
public void onActivityResult(int requestCode, int resultCode, Intent data){
//This is where you can use the data from the previous intent to set the image view
Uri uri = data.getData();
im.setImageURI(uri); //where im is your imageview
}
Check out this link for a more detailed answer.
My aim is to get the available TTS voice engines and their respective available languages. I can successfully get the available engines with the following code:
PackageManager pm = getPackageManager();
Intent ttsIntent = new Intent();
ttsIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
// ttsIntent.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
ArrayList<String> engineInfo = new ArrayList<String>();
List<ResolveInfo> list = pm.queryIntentActivities(ttsIntent,
PackageManager.GET_META_DATA);
for (ResolveInfo appInfo : list) {
engineInfo.add(appInfo.activityInfo.applicationInfo.packageName);
engineInfo.add(appInfo.activityInfo.applicationInfo.loadLabel(pm)
.toString());
}
The commented out line above requests a data extra for the intent. I've done many searches, but can find no examples of queryIntentActivities() using 'getExtra' or 'putExtra' in this way.
Can I therefore assume, that this isn't an option and I need to go back to read some basics somewhere of why this isn't possible!?
If I make any attempt to retrieve the data, I get a null pointer, even if I'm checking if it's !=null which is a little odd?
appInfo.activityInfo.applicationInfo.metaData.getEtcEtc...
Any help would be much appreciated, thanks in advance.
postPutEXTRA: API level is 9 so getEngines() isn't available to me, although that would involve initialising a tts object for each one, so not ideal anyway.
postPutEDIT: I tried the following within the loop to extract associated uid's, but it didn't work for all but one of the voice engines I tested.
ApplicationInfo ai = null;
try {
ai = pm.getApplicationInfo(appInfo.activityInfo.applicationInfo.packageName, 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
return;
}
String[] voices = pm.getPackagesForUid(ai.uid);
I've gone through a similar process, but if I remember correctly, you can't simply fire of an intent query to get the available languages. That absolutely makes sense, because what languages are available depends on what voices and other packages the user will have installed with the TTS engine. In other words: it's not some static parameter that you query for, but requires to be resolved at runtime.
In order to get the available voices (and other information) for a TTS engine, you'll need to actually start/initialise it. That means you need to do something like:
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, MY_DATA_CHECK_CODE);
And then retrieve the data in onActivityResult(...):
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != MY_DATA_CHECK_CODE) return;
// here you should be able to get the info from 'data', i.e.
ArrayList<String> voices = getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
}
Note that I copy-pasted most of above from an article on the Android dev blog.
It's been a while since I last played with TTS, so if above doesn't work out, feel free to leave a comment and I'll have a closer look at the implementation I made back then.
I couldn't solve the specific title of this question, but I did get the information I wanted of the available voice engines and their languages on the device, without user interaction, in the following way:
private void getEngines() {
Intent ttsIntent = new Intent();
ttsIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
ArrayList<String> engineInfo = new ArrayList<String>();
PackageManager pm = getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(ttsIntent,
PackageManager.GET_META_DATA);
for (ResolveInfo appInfo : list) {
engineInfo.add(appInfo.activityInfo.applicationInfo.packageName);
ttsIntent
.setPackage(appInfo.activityInfo.applicationInfo.packageName);
ttsIntent
.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
startActivityForResult(ttsIntent, 33);
}
if (DE.BUG) {
MyLog.d("engineInfo: " + engineInfo.size() + " : "
+ engineInfo.toString());
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 33 && data != null) {
ArrayList<String> available = data
.getStringArrayListExtra("availableVoices");
if (DE.BUG) {
MyLog.i("Voices: " + available.toString());
}
}
}
I've left in my custom logging class/method to show where I'm capturing the data. You can rearrange the above to put all of the data into one or more global array list(s).