android.os.TransactionTooLargeException when executing queryIntentActivities - java

I am using a device with Android 5.0.1 and when execute the function:
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
List<ResolveInfo> apps = manager.queryIntentActivities(mainIntent,0);
An exception of type TransactionTooLargeException is thrown ....
I cannot use the flag: MATCH_DEFAULT_ONLY because I can't be limited to only those activities that support the CATEGORY_DEFAULT.
Looks that the problem is related with the amount of data returned, like many related issues that I found here at stackoverflow ...
Is there a way to break this response? Or is there a query, equivalent, or a combination of flags that allow get the same results making more than one query?
I am asking because is not clear to me the meaning of flag = 0 in documentation: https://developer.android.com/reference/android/content/pm/PackageManager.html#queryIntentActivities(android.content.Intent, int)
May be I can query more than once, with different queries, and combine the results?

You could chunk your query by being a bit more specific with how you form your Intent, and do multiple queries rather than 1 lump sum query. Right now, you're querying basically every application (potentially multiple for each) on the device (since everything that can launch will have a android.app.action.Main action), which goes over your allocation of the ~1 MB shared buffer parcel limit causing the exception:
From the docs:
{ action=android.app.action.MAIN } matches all of the activities that can be used as top-level entry points into an application.
{ action=android.app.action.MAIN, category=android.app.category.LAUNCHER } is the actual intent used by the Launcher to populate its top-level list.
So, adding a category narrows the search results. If you need everything of action type "android.app.action.Main", use that as the action, then iterate over an additional set of categories ex sudo code:
List<ResolveInfo> activities = new ArrayList<ResolveInfo>()
String[] categories = new String[] { android.app.category.LAUNCHER, additional... }
for(String category: categories){
Intent mainIntent = new Intent(Intent.ACTION_MAIN)
mainIntent.addCategory(category)
List<ResolveInfo> matches = manager.queryIntentActivities(mainIntent, 0); //You might want to use a better #ResolveInfoFlag to narrow your ResolveInfo data
if(!matches.isEmpty()){
activities.addAll(matches)
}
}
You should also probably do a try catch, because when doing a MATCH_ALL, there's still no guarantee that the query won't be too large. There might be appropriate flags to help with this, but I'm not familiar with your use case.

Related

How to get current activity correctly?

I need to access current activity of my app. I checked this official code
If i use this code below as documentation says:
PythonActivity = autoclass('org.renpy.android.PythonActivity')
i can't access activity and it returns error. So after searching i found this:
PythonActivity = autoclass('org.kivy.android.PythonActivity')
There is 2 function below. First one is opening webbrowser just like in documentation and IT WORKS:
from jnius import cast
from jnius import autoclass
PythonActivity = autoclass('org.kivy.android.PythonActivity')
Intent = autoclass('android.content.Intent')
Uri = autoclass('android.net.Uri')
intent = Intent()
intent.setAction(Intent.ACTION_VIEW)
intent.setData(Uri.parse('http://kivy.org'))
currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
currentActivity.startActivity(intent)
Now i need to recode this for my own script. I want to add new contact to Contacts. I got 'WRITE_CONTACTS' permission for that. in buildozer.spec:
android.permissions = WRITE_CONTACTS
But my own script returns me error. My function:
from jnius import autoclass,cast
PythonActivity=autoclass('org.kivy.android.PythonActivity')
Intent = autoclass('android.content.Intent')
JS=autoclass('java.lang.String')
intent=Intent()
intent.setAction(Intent.ACTION_INSERT)
intent.setType('ContactsContract.RawContacts.CONTENT_TYPE')
intent.putExtra('ContactsContract.Intents.Insert.NAME',JS('Example Name'))
intent.putExtra('ContactsContract.Intents.Insert.PHONE',JS('7777777777'))
currentActivity=cast('android.app.Activity',PythonActivity.mActivity)
currentActivity.startActivity(intent)
Log:
jnius.jnius.JavaException: JVM exception occurred: No Activity found
to handle Intent { typ=ContactsContract.RawContacts.CONTENT_TYPE (has
extras) } android.content.ActivityNotFoundException
I don't know that Is this error eccours because of activity or my wrong type selection. I would be glad if anyone help me to handle this. Like this contents on media is very few. So i hope like this topics let people learn quickly just like me.
I don't know the Python syntax, but I can tell you what the problem is:
You are missing the Intent ACTION. You need to set the ACTION in the Intent to ContactsContract.Intents.Insert.ACTION (which has the string value "android.intent.action.INSERT")
Also, it looks like you are sending Android constant names instead of the actual string values of these things, for example:
you do this:
intent.setType('ContactsContract.RawContacts.CONTENT_TYPE')
which sets the type in the Intent to the string value "ContactsContract.RawContacts.CONTENT_TYPE", which you can see here:
jnius.jnius.JavaException: JVM exception occurred: No Activity found
to handle Intent { typ=ContactsContract.RawContacts.CONTENT_TYPE (has
extras) } android.content.ActivityNotFoundException
but the type needs to be set to the Android constant ContactsContract.RawContacts.CONTENT_TYPE which has the string value "vnd.android.cursor.dir/raw_contact"
As I said, I don't know Python syntax, but you probably need to do something like this:
intent=Intent()
intent.setAction(ContactsContract.Intents.Insert.ACTION)
intent.setType(ContactsContract.RawContacts.CONTENT_TYPE)
or, if Python supports it, Intent has a constructor that takes an ACTION as a parameter, so this might also work:
intent=Intent(ContactsContract.Intents.Insert.ACTION)
intent.setType(ContactsContract.RawContacts.CONTENT_TYPE)
Let me know if any of this makes sense.
FINALLY, I found solution to access activities and getting Contacts datas.
Accessing correctly PythonActivity:
PythonActivity = autoclass("org.kivy.android.PythonActivity")
Accessing Contacts Java Class:
Contacts = autoclass("android.provider.ContactsContract$Contacts")
Accessing ContactsContract.CommonDataKinds.Phone Java Class (same):
Kinds = autoclass("android.provider.ContactsContract$CommonDataKinds$Phone")
Creating getContentResolver():
cr = PythonActivity.mActivity.getContentResolver()
Now we need to create 2 query. In first, we are going to find ID and need to use it in second query to find equal NUMBER. Also need to check is contact has phone number? too. After that we can loop second query to find number:
name_number_dict= {} # Name:Number # Change as you wish..
while (que1.moveToNext()): #Query 1 Loop
if que1.getString(que1.getColumnIndex(Contacts.HAS_PHONE_NUMBER)): #If has number
que2 = cr.query(Kinds.CONTENT_URI, None, Kinds.CONTACT_ID + " = " + que1.getString(que1.getColumnIndex(CONTACTS_ID)), None, None) #Query 2 Loop
while (que2.moveToNext()):
name_number_dict[que1.getString(que1.getColumnIndex(Contacts.DISPLAY_NAME))] = que2.getString(que2.getColumnIndex(Kinds.NUMBER) # Get datas and put in dic.
If you need to check all ColumnNames of query, use query1.getColumnNames() >> Referance
I hope it helps you guys who need to use this in Python>Kivy applications..

Android massive database creation

So guys, this is a big one
I'm currently working on an App which involves a big database creation.
Basicalls what it does is scanning the entire device for any kind of media file and adds it to the database (filtered by certain file extensions).
The way it currently does that is scanning through the device's folders with a recursive function, and every time it finds a file that is actually a file and not a directory AND matches the file extensions I picked it adds the file's path to a previously created ArrayList containing only strings.
This is implemented in an AsyncTask so it's done in the background.
As soon as that task finishes it passes the ArrayList on to another AsyncTask that is responsible for adding those files to the database. Out of simplicity resons I only took the file's path, its name and the title that is stored in its tags for every entry (simple sqlite stuff) and displayed the titles in a ListView in another activity over cursor adapters and so on (which isnt really an issue in my case).
Right now it workes pretty well, doesn't crash or return anything bad (I'm testing on a OnePlus One right now).
BUT I have a really big music library on my phone (around 5400 songs) which is nearly filling my entire storage. And out of some unknown reason to me, the scanning task i mentioned (with the ArrayList) stops at around 3000 files and passes the ArrayList on to the second task, which is obviously not what I'm aming at to have.
My guess is that there might be a big performance issue and using an ArrayList is not the best way to approach this. It might work with a number of files that is under 3000 but higher than that is also necessary...
So what would you guys suggest? Does my approach lack something or do I have to try something completely different?
You don't need scan your folders (and, probably, creating database of media files too), you can just use MediaStore class
The Media provider contains meta data for all available media on both internal and external storage devices.
For example, this piece of code will return all available audio playlists
private List<Playlist> getPlayListsList(){
ContentResolver resolver = getContentResolver();
Uri uri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
String[] columns = { _ID, NAME };
Cursor playLists = resolver.query(uri, columns, null, null, null);
List<Playlist> result = new ArrayList<>();
if (playLists == null) {
return result;
}
Cursor cursor;
for (boolean hasItem = playLists.moveToFirst(); hasItem; hasItem = playLists.moveToNext()) {
long id = playLists.getLong(playLists.getColumnIndex(_ID));
String name = playLists.getString(playLists.getColumnIndex(NAME));
cursor = resolver.query(MediaStore.Audio.Playlists.Members.getContentUri("external", id),
new String[]{MediaStore.Audio.Playlists.Members.TITLE, MediaStore.Audio.Playlists.Members.ARTIST,
MediaStore.Audio.Media.DATA, MediaStore.Audio.Playlists.Members.PLAY_ORDER}, null, null, null
);
int songsCount = cursor.getCount();
result.add( new Playlist(
id,
name,
songsCount));
cursor.close();
}
playLists.close();
return result;
}

Get Chosen App from Intent.createChooser

I am trying to capture the result of Intent.createChooser to know which app a user selected for sharing.
I know there have been a lot of posts related to this:
How to know which application the user chose when using an intent chooser?
https://stackoverflow.com/questions/6137592/how-to-know-the-action-choosed-in-a-intent-createchooser?rq=1
How to get the user selection from startActivityForResult(Intent.createChooser(fileIntent, "Open file using..."), APP_PICKED);?
Capturing and intercepting ACTION_SEND intents on Android
but these posts are somewhat old, and I am hoping that there might be some new developments.
I am trying to implement a share action without having it be present in the menu. The closest solution to what I want is provided by ClickClickClack who suggest implementing a custom app chooser, but that seems heavy handed. Plus, it seems like there might be some Android hooks to get the chosen app, like the ActivityChooserModel.OnChooseActivityListener.
I have the following code in my MainActivity, but the onShareTargetSelected method is never getting called.
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, shareMessage());
sendIntent.setType("text/plain");
Intent intent = Intent.createChooser(sendIntent, getResources().getText(R.string.share_prompt));
ShareActionProvider sap = new ShareActionProvider(this);
sap.setShareIntent(sendIntent);
sap.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener() {
#Override
public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
System.out.println("Success!!");
return false;
}
});
startActivityForResult(intent, 1);
As of API level 22 it is now actually possible. In Android 5.1 a method (createChooser (Intent target, CharSequence title, IntentSender sender)) was added that allows for receiving the results of the user's choice. When you provide an IntentSender to createChooser, the sender will be notified by the chooser dialog with the ComponentName chosen by the user. It will be supplied in the extra named EXTRA_CHOSEN_COMPONENT int the IntentSender that is notified.
I am trying to capture the result of Intent.createChooser to know which app a user selected for sharing.
That is not possible.
Other "choosing" solutions, like ShareActionProvider, may offer more. I have not examined the Intent handed to onShareTargetSelected() to see if it contains the ComponentName of the chosen target, though the docs suggest that it should.
And, if for some reason it does not, you are welcome to try to fork ShareActionProvider to add the hooks you want.
The reason why createChooser() cannot be handled this way is simply because the "choosing" is being done by a separate process from yours.
I have the following code in my MainActivity, but the onShareTargetSelected method is never getting called.
ShareActionProvider goes in the action bar. You cannot just create an instance, call a couple of setters, and expect something to happen.

Trying to pass an int between two classes on Android platform

I'm trying to pass an int between two classes using a bundle. I have looked online and it seems like it should be easy to do, its just not working for me :( any help is much appreciated! I am trying to pass the int life from one page to another in a simple game.
here is the code for the first page where I am passing it from
int life = 5;
....
if(input.equalsIgnoreCase(teststr))
{
Intent i = new Intent("com.name.PAGETWO");
i.putExtra("com.name.PAGETWO.life", life);
startActivity(i);
mpButtonClick.stop();
}
And on the second page, trying to receive the int
Intent i = new Intent("com.name.PAGETWO");
Bundle extras = i.getExtras();
int temp2 = extras.getInt("life");
int life = temp2;
when i try to run it, it tries to go on to the second page and then just says the application has unexpectedly stopped, but when i comment out the code it continues running as normal, so i know i am definitely putting in the code here wrong somewhere. thanks in advance!
Why would a brand new intent object contain the int you've passed? In the receiver activity, use getIntent() to retrieve the intent that the activity was started with. That intent would contain your extras.
You're creating a brand new Intent on the second page, which of course doesn't have the bundle. Instead, you need to retrieve the calling Intent using getIntent().
Here's how your second page's code could look:
int life = getIntent().getExtras().getInt("life");
EDIT: Looking at your code again, make sure the key name of the extra is consistent on both ends. To be sure, I usually use a final variable for this. So in your second activity (let's say it's ActivityTwo), declare this at the top (outside of onCreate()):
final static public String EXTRA_LIFE = "life";
Then in the calling activity:
Intent i = new Intent(this, ActivityTwo.class);
i.putExtra(ActivityTwo.EXTRA_LIFE, life);
startActivity(i);
(You may have also been constructing your Intent incorrectly; compare my example).
And in the second activity, inside of onCreate():
int life = getIntent().getIntExtra(EXTRA_LIFE, 0);
(You can replace 0 with what you want the default value to be if the extra doesn't exist.)
That way you don't have to worry about typos and can rely on Eclipse's suggestions to make sure you're consistent.

EXTRA_OUTPUT ignored on Eris, causes data to return null on G1

So I'm trying to launch the Camera activity using the following code:
//In public void captureImage()
...
Intent cameraIntent = new Intent(MediaStore.ACTION_CAPTURE_IMAGE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(new File("/sdcard/image.jpg")));
startActivityForResult(cameraIntent, REQUEST_CAMERA);
And then to handle the result:
//In onActivityResult()
...
case REQUEST_CAMERA:
Intent intent = new Intent (CurrentScreen.this, NextScreen.this);
intent.putExtra(data);
startActivity(intent);
CurrentScreen.this.finish();
...
Where I use intent.putExtra(data) to attach the small bitmap to the intent, and use it as a thumbnail in the next activity, and the full sized file is supposedly saved as /sdcard/image.jpg.
This is the expected behavior (according to the documentation), to have a small bitmap for a thumbnail, and a large file saved. However when testing this on a G1 and an Eris, I have been seeing some strange behavior.
On the G1:
Although the resultCode shows RESULT_OK, the intent data that is returned to the result handler is null.
Also EXTRA_OUTPUT seems to be completely ignored, I have no idea where it is saving the image.
On the Eris:
The intent data comes back OK
EXTRA_OUTPUT is also ignored, but it is saving the images to the regular media store at /sdcard/dcim/100media
So my question is this: is there any way to get consistent behavior for what I am trying to do using the standard camera activity? I could write up a custom activity to try and get it to work the way I want, but I'd prefer to avoid that route.
I do not have answers to your question as I am new to the Java/Android development world. But I am attempting something similar to what you are doing, except I want to simply take the picture then attach it to an Email message.
I implemented part of your example and was able to verify that the camera created the file I specified and that if I use the same file name for the next picture that it overwrites the previous file which is what I would expect.
But what I was really going to say is perhaps you will have to test if the pat "/sdcard/..." actually exists or not. Also you could possibly simplify your process by passing the path to the next activity.
Good Luck,
Jamie Irwin

Categories