Why is no Activity found to handle Intent?
Asked Answered
P

8

9

Instead of going the regular getPackageManager().getLaunchIntentForPackage("com.example.app") way, I want to create the launch intent by myself.

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage("com.example.app");
startActivity(intent);

Why does Android not find the Activity, if the com.example.app is installed, enabled and has a correct manifest? (It works perfectly with getLaunchIntentForPackage.)

Patel answered 2/2, 2019 at 11:40 Comment(7)
com.example.app check this with package="com.example...." inside manifest file.Adal
kindly visit: https://mcmap.net/q/350253/-how-to-set-default-app-launcher-programmaticallyAdal
@Opriday The com.example.app's manifest file is correct, it contains the right package name (com.example.app). This is the same package name I'm trying to use with intent.setPackage("com.example.app");. No typo.Racism
@Opriday I've visited your link, but I can't find anything relevant there. What piece of info should I look for?Racism
I've considered using intent.setComponent(...), but it shouldn't be needed according to the documentation: "(Usually optional) Explicitly set the component to handle the intent. If left with the default value of null, the system will determine the appropriate class to use based on the other fields (action, data, type, categories) in the Intent. (...) You should only set this value when you know you absolutely want a specific class to be used; otherwise it is better to let the system find the appropriate class so that you will respect the installed applications and user preferences."Racism
+1 . This is a good question actually. Makes we wonder whats the difference between your intent and intent created by getLaunchIntentForPackage(). Try Log.d(TAG, intent.toString() + " vs " + intent2.toString()). (I added my workaround as answer.)Eusebioeusebius
remove this line : intent.addCategory(Intent.CATEGORY_LAUNCHER);Rearmost
E
5

I understand that you are trying to start the Launcher activity of a known application with known package name (com.example.app). I assume you have information about the application. Thus, you can start it via explicit intent like so:

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.app", "com.example.app.MainActivity"));
if(intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}

EDIT: Doing an analysis of the two intent objects (intent1 == your own intent VS intent2 == intent created from getLaunchIntentForPackage()), the difference is

intent1:

{ act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] pkg=com.example.app }

intent2:

{ act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.example.app cmp=com.example.app/.MainActivity }

I will have to believe that what you have done to create your own intent object is not enough for the explicit intent to work. You will have to provide Android more information about your intent such as being specific with the component name (as shown in my answer above).

Eusebioeusebius answered 7/2, 2019 at 5:1 Comment(7)
+1 for your help, you understand the goal of my example code, and resolveActivity lead me to its documentation full of useful details. But your answer is not about the goal of my question: to gain a deeper understanding of why the activity is not found. My own answer has already had a workaround using setClassName (same as your setComponent, just more convenient). You had a great idea in your comment (comparing the intents), but your answer currently doesn't contain new details, just a subset of the known info of my answer.Racism
(By the way there's a comment right above yours starting with "I've considered using intent.setComponent(...), but", which is relevant to this answer.)Racism
Thanks. I do recognise that this is not the answer to your goal. I do not have enough time at the moment so I gave you what's off the top of my head. I will get back to you later today. Cheers!Eusebioeusebius
I updated my answer. Looking at your explicit intent VS intent created by getLaunchIntentForPackage(), it looks like you are missing values for your intent to work. setClassName() or setComponent() should be used to complete the information about your intent.Eusebioeusebius
Useful to see the differences: the 0x10000000 flag (Intent.FLAG_ACTIVITY_NEW_TASK) and the component name. This proves that the system should find the right activity in both cases. But it doesn't, so the question remains: why. The activity exists, even a child could find it (knowing the action, category and package). Is the system looking for what I tell it to look for? We already have the answer: no, it's looking for the default category too. But it's still unclear why. Why look for the default category in case of an explicit intent?Racism
using the setComponent() as above, its creates an intent with the following info only: { cmp=com.example.app/.MainActivity } and it finds the correct activity and it does not use CATEGORY_DEFAULTEusebioeusebius
setComponent works. That's clear, I agree with you. (setClassName works too.) But instead of finding a working method, my goal at first is to find out why the code example in my question doesn't work. Why is a component name required? The package name should be enough, beacuse it makes the intent explicit. I have an answer for this, but more details are needed.Racism
J
1

'To receive implicit intents, you must include the CATEGORY_DEFAULT category in the intent filter.' - Does your receiving app have this?

Example:

<activity android:name="ShareActivity">
     <intent-filter>
         <action android:name="android.intent.action.SEND"/>
         <category android:name="android.intent.category.DEFAULT"/>
         <data android:mimeType="text/plain"/>
     </intent-filter>
</activity>

Excerpt from: https://developer.android.com/guide/components/intents-filters#Receiving

You can also check to make sure there is an activity that can receive your broadcast:

 PackageManager packageManager = getPackageManager();
 List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
 boolean isIntentSafe = activities.size() > 0;

Excerpt from: https://developer.android.com/training/basics/intents/sending#java

Justajustemilieu answered 2/2, 2019 at 13:35 Comment(3)
It is not an implicit intent. It is explicit, because the package name is specified using intent.setPackage("com.example.app"). "Explicit intents specify which application will satisfy the intent, by supplying either the target app's package name or a fully-qualified component class name." - sourceRacism
The receiving app does not have the DEFAULT category in its LAUNCHER intent filter, only somewhere else.Racism
If I try a different receiver app which has DEFAULT category, that starts perfectly. But DEFAULT shouldn't be neccesary, because my intent is explicit. If I can find the right activity by looking at the manifest of the receiver app, why can't Android do it for me? It knows the category, the action, and the package name as well. If getLaunchIntentForPackage works perfectly without DEFAULT, my approach should work too.Racism
P
1

startActivity treats all intents as if they declared CATEGORY_DEFAULT

  • Even if you don't have intent.addCategory(Intent.CATEGORY_DEFAULT); in your code.

  • Even if you add intent.removeCategory(Intent.CATEGORY_DEFAULT);.

  • Even if your intent is explicit*: intent.setPackage("com.example.app");.
    * Supplies "either the target app's package name or a fully-qualified component class name".

...except if it doesn't

The system will not look for CATEGORY_DEFAULT if you set the class name of the target activity:

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClassName("com.example.app", "com.example.app.NameOfTheActivityToBeStarted");
startActivity(intent);

Source of header: the blue note on the <category> element's page.
Source of the definition of an explicit intent: developer.android.com.

Patel answered 6/2, 2019 at 11:17 Comment(1)
The system will find the activity even if the intent doesn't have action or category, like: Intent intent = new Intent(); intent.setClassName("com.example.app", "com.example.app.NameOfTheActivityToBeStarted"); startActivity(intent);Racism
T
1

This is the function where android.content.Intent#CATEGORY_DEFAULT is added to all startActivity code.

ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags) {
        try {
            return AppGlobals.getPackageManager().resolveIntent(intent, resolvedType,
                    PackageManager.MATCH_DEFAULT_ONLY | flags
                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
        } catch (RemoteException e) {
        }
        return null;
    }

/**
     * Resolution and querying flag: if set, only filters that support the
     * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for
     * matching.  This is a synonym for including the CATEGORY_DEFAULT in your
     * supplied Intent.
     */
    public static final int MATCH_DEFAULT_ONLY  = 0x00010000;

This is code from where everything starts http://androidxref.com/7.1.2_r36/xref/frameworks/base/core/java/android/app/ContextImpl.java#766

Triplett answered 8/2, 2019 at 12:24 Comment(1)
+1. It would be great to see the code that (runs after startActivity is called and) ignores the flags when the component is not null (making the setComponent and setClassName methods work instead of the insufficient setPackage approach). I guess that will be the same logic as described here, but I'm not sure and can't find the code. We are really close now to fully understand the behavior of the platform, on a code level.Racism
H
1

You asked to see the code executed after startActivity and here it is.

In your app:
Activity.startActivity(Intent) calls
Activity.startActivity(Intent, Bundle), which calls
Activity.startActivityForResult(Intent, int), which calls
FragmentActivity.startActivityForResult(Intent, int), which calls
Activity.startActivityForResult(Intent, int), which calls
Activity.startActivityForResult(Intent, int, Bundle), which calls
Instrumentation.execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle), which calls IActivityManager.startActivity(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle)

The call on the last line is a remote process call, meaning that in your app process a the method is called on a proxy IActivityManager instance which forwards it to another process, in this case a system process.

Up to this point, no Intent filtering has taken place.

In Android's system process IActivityManager resolved to ActivityManagerService and:

ActivityManagerService.startivity(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle) calls
ActivityManagerService.startActivityAsUser(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle, int), which calls
ActivityStackSupervisor.startActivityMayWait(IApplicationThread, int, String, Intent, String, IVoiceInteractionSession, IVoiceInteractor, IBinder, String, int, int, ProfilerInfo, WaitResult, Configuration, Bundle, boolean, int, IActivityContainer, TaskRecord), which calls
ActivityStackSupervisor.resolveActivity(Intent, String, int, ProfilerInfo, int), which calls
IPackageManager.resolveIntent(Intent, String, int, int)

This is the where MATCH_DEFAULT_ONLY is added, as nkalra0123 said.

Also, this is another remote method invocation. IPackageManager gets resolved to PackageManagerService, and from there it goes like this:

PackageManagerService.resolveIntent(Intent, String, int, int) calls
PackageManagerService.queryIntentActivities(Intent, String, int, int), which attempts to get all the Activities for the Intent package. This gets the Activities from your package and then calls
PackageService.ActivityIntentResolver.queryIntentForPackage(Intent, String, int, ArrayList<PackageParser.Activity>, int), which gets the IntentFilters in your package and then calls
PackageService.ActivityIntentResolver.queryIntentFromList(Intent, String, boolean , ArrayList<F[]>, int), which calls
IntentResolver.buildResolveList(...), which runs all the IntentFilters it found against the data in your Intent, taking into account whether or not we need CATEGORY_DEFAULT, and adding the matching IntentFilters to a list accordingly.

All these call method calls then return and eventually some object somewhere will figure out there were no matching IntentFilters. I omit that here because this is the relevant part of the answer.

Haematocele answered 11/2, 2019 at 20:23 Comment(0)
T
0

You need to create a component name for the needed app such as:

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(ComponentName.createRelative("com.whatsapp",".Main"));
intent.setPackage("com.whatsapp");

The component name represents the activity that you need to open, full package name and the second parameter is the class name for that package.

Tiernan answered 11/2, 2019 at 21:11 Comment(0)
C
0

Let me add some additional info in this. as described here, Without a component name, the intent is implicit.

Component name is optional, but it's the critical piece of information that makes an intent explicit, meaning that the intent should be delivered only to the app component defined by the component name. Without a component name, the intent is implicit and the system decides which component should receive the intent based on the other intent information (such as the action, data, and category—described below). If you need to start a specific component in your app, you should specify the component name.

The DEFAULT category is required for the Context.startActivity method to resolve your activity when its component name is not explicitly specified. Check first example in this link.

Hope this will help.

Currant answered 13/2, 2019 at 9:38 Comment(0)
P
0

I think an easier way is to query&prepare the Intent based on its result. Here's how it's done, for example, to launch WhatsApp app:

manifest:

<queries>
    <package android:name="com.whatsapp" />
</queries>

code:

val whatsAppIntent =Intent(Intent.ACTION_MAIN).setPackage("com.whatsapp").addCategory(Intent.CATEGORY_LAUNCHER)
binding.whatsAppChip.setOnClickListener {
    val resolvedActivity: ResolveInfo? =packageManager.resolveActivity(whatsAppIntent,  0)
    if(resolvedActivity==null){
        // TODO handle case we can't find the app
        return@setOnClickListener
    }
    startActivity(Intent(whatsAppIntent).setComponent(ComponentName(resolvedActivity.activityInfo.packageName,
            resolvedActivity.activityInfo.name)))
}
Precipitin answered 25/7 at 21:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.