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.)

Tamás Bolvári
  • 2,976
  • 6
  • 34
  • 57
  • com.example.app check this with package="com.example...." inside manifest file. – Shahzad Afridi Feb 02 '19 at 11:44
  • kindly visit: https://stackoverflow.com/a/30617251/6672577 – Shahzad Afridi Feb 02 '19 at 11:47
  • 1
    @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. – Tamás Bolvári Feb 02 '19 at 11:49
  • @Opriday I've visited your link, but I can't find anything relevant there. What piece of info should I look for? – Tamás Bolvári Feb 02 '19 at 11:50
  • 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._" – Tamás Bolvári Feb 02 '19 at 12:05
  • 1
    +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.) – user1506104 Feb 07 '19 at 05:50
  • remove this line : intent.addCategory(Intent.CATEGORY_LAUNCHER); – Abdul Aziz Feb 13 '19 at 09:10

7 Answers7

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).

user1506104
  • 6,554
  • 4
  • 71
  • 89
  • 1
    +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](https://stackoverflow.com/a/54552362/1293492) 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. – Tamás Bolvári Feb 07 '19 at 08:32
  • 1
    (By the way there's a comment right above yours [starting with](https://stackoverflow.com/questions/54492708/why-is-no-activity-found-to-handle-intent/54552362) "_I've considered using intent.setComponent(...), but_", which is relevant to this answer.) – Tamás Bolvári Feb 07 '19 at 09:07
  • 1
    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! – user1506104 Feb 07 '19 at 09:11
  • 1
    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. – user1506104 Feb 07 '19 at 12:05
  • 1
    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](https://stackoverflow.com/a/54552362/1293492)**. But it's still unclear why. Why look for the default category in case of an explicit intent? – Tamás Bolvári Feb 07 '19 at 12:16
  • 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_DEFAULT – user1506104 Feb 07 '19 at 12:34
  • 1
    `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](https://stackoverflow.com/questions/54492708/why-is-no-activity-found-to-handle-intent/54552362#54552362)**, but more details are needed. – Tamás Bolvári Feb 07 '19 at 13:26
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

AzraelPwnz
  • 506
  • 2
  • 12
  • 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._" - [source](https://developer.android.com/guide/components/intents-filters#Types) – Tamás Bolvári Feb 02 '19 at 13:45
  • The receiving app does not have the `DEFAULT` category in its `LAUNCHER` intent filter, only somewhere else. – Tamás Bolvári Feb 02 '19 at 13:50
  • 1
    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. – Tamás Bolvári Feb 02 '19 at 14:05
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.

Tamás Bolvári
  • 2,976
  • 6
  • 34
  • 57
  • 2
    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);` – Tamás Bolvári Feb 06 '19 at 11:48
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

nkalra0123
  • 2,281
  • 16
  • 17
  • 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](https://developer.android.com/reference/android/content/Intent#resolveActivity(android.content.pm.PackageManager)), 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. – Tamás Bolvári Feb 08 '19 at 13:05
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.

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.

Khalid Taha
  • 3,183
  • 5
  • 27
  • 43
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.

Pravin Divraniya
  • 4,223
  • 2
  • 32
  • 49