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