I need to start a few services provided by other apps, let them execute whatever the want to execute, and kill them once they call me back and I receive the data collected. Sadly startService(serviceIntent); crashes with
Unable to start activity ComponentInfo{…}: java.lang.IllegalStateException: Not allowed to start service Intent {…}: app is in background uid UidRecord{70fcf01 u0a90 CEM bg:+1h49m29s56ms idle procs:1 seq(0,0,0)}
It appears that this is by design, here's the crashing AOSP code.
My code crashing looks like this:
final PackageManager pm = getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory("my.special.service.CATEGORY");
List<ResolveInfo> results = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
for (ResolveInfo info : results) {
ComponentName name =
new ComponentName(info.serviceInfo.applicationInfo.packageName, info.serviceInfo.name);
Intent serviceIntent = new Intent();
serviceIntent.setComponent(name);
startService(serviceIntent); // 💥 K A B O O M
bindService(serviceIntent, boundServiceConnection, BIND_AUTO_CREATE);
}
The way service is defined in another app's Manifest.xml:
<service
android:name=".ServiceFromAnotherApp"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<categoy android:name="my.special.service.CATEGORY" />
</intent-filter>
</service>
Any ideas if there're any workarounds, maybe different architecture?
Core trait that I want to retain is:
to be able to create a service
tell it to fetch additional data (potentially several times during its lifecycle)
get callback from the service once it finishes every subsequence fetch
Related
I have a little personal app I built which allows me to specify different browsers for different URLs. Up until Android 13, it was working fine, but at some point after Android 13 it started failing. I suspect it's related to the app's authority (or lack thereof) to launch an arbitrary Activity, but wading through the docs has yielded zilch.
The process works like this:
I query all activities for an Intent that has a URI as its data property
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri); // uri is some location like 'https://www.google.com'
PackageManager pm = context.getPackageManager();
List<ResolveInfo> allTargets = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
I loop through allTargets looking for the browser I want based on its name:
ResolveInfo target = null;
for (ResolveInfo b : allTargets) {
String appName = b.loadLabel(pm).toString();
// targetBrowserName will be something like "Chrome"
if(appName.equalsIgnoreCase(targetBrowserName)) {
target = b;
break;
}
}
I then attempt to launch this browser, with the url
ActivityInfo activity = target.activityInfo;
ComponentName name = new ComponentName(activity.applicationInfo.packageName, activity.name);
targetIntent = new Intent(Intent.ACTION_MAIN);
targetIntent.addCategory(Intent.CATEGORY_LAUNCHER);
targetIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
targetIntent.setComponent(name);
targetIntent.setData(uri);
startActivity(targetIntent);
This now fails with an error like:
android.content.ActivityNotFoundException: Unable to find explicit activity class {com.android.chrome/com.google.android.apps.chrome.IntentDispatcher}; have you declared this activity in your AndroidManifest.xml, or does your intent not match its declared <intent-filter>?
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4803)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4836)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:54)
at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2308)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7898)
at java.lang.reflect.Method.invoke(Native Method)
I've tried various permutations of the launch code (which, as a reminder, was working fine). E.g.
targetIntent = pm.getLaunchIntentForPackage(activity.applicationInfo.packageName);
targetIntent.setAction(Intent.ACTION_VIEW);
targetIntent.addCategory(Intent.CATEGORY_BROWSABLE);
targetIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
But I still get the same error (although in the above case with a different Activity class that is still fails to find)
I understand there are constraints on App visibility, but I assumed I was covered as I have this in my AndroidManifest.xml
<!-- As per guidelines, QUERY_ALL_PACKAGES is required to list all browsers -->
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
Reading the docs, I noted that I didn't have a <queries> element in the manifest (is that new?), so I added this:
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
No joy.
Does anyone know the correct way to launch a known/specific [browser] app based programmatically? Or maybe what changed in Android 13 to make this code work again?
Thanks!
Edit following correct answer below
The guidance provided in the answer below worked. This is a summarized version of the final code:
// Create an intent with the destination URL
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
// List all activities that support this intent, and choose one:
PackageManager pm = context.getPackageManager();
List<ResolveInfo> allTargets = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
ResolveInfo target = null;
for (ResolveInfo b : allTargets) {
String appName = b.loadLabel(pm).toString();
// targetBrowserName is something like "Chrome"
if(appName.equalsIgnoreCase(targetBrowserName)) {
target = b;
break;
}
}
// Set the specific component to be launched
ActivityInfo activity = target.activityInfo;
ComponentName name = new ComponentName(activity.applicationInfo.packageName, activity.name);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
intent.setComponent(name);
// Start
startActivity(intent);
Seems to be related to this behavior change https://developer.android.com/about/versions/13/behavior-changes-13#intent-filters
When your app sends an intent to an exported component of another app that targets Android 13 or higher, that intent is delivered if and only if it matches an element in the receiving app. Non-matching intents are blocked.
To make sure this is indeed your issue check logcat for the tag PackageManager. Something like W/PackageManager( 1828): Access blocked: ComponentInfo{com.android.chrome/com.google.android.apps.chrome.Main} should appear there
I didn't look too much into how it works yet, but seems like now we need to query external activities before using them unless they don't have intent-filters. That's going to "activate" the component and then the intent you use needs to "match" the other apps intent-filter:
Intents are matched against intent filters not only to discover a target component to activate, but also to discover something about the set of components on the device. For example, the Home app populates the app launcher by finding all the activities with intent filters that specify the ACTION_MAIN action and CATEGORY_LAUNCHER category. A match is only successful if the actions and categories in the Intent match against the filter, as described in the documentation for the IntentFilter class.
https://developer.android.com/guide/components/intents-filters#imatch
I was having a similar issue. The reason for it not to work was that I modified the Uri by calling intent.setData after the call to queryIntentActivities. Seems like this is invalidating the activation of the component.
Summary: not calling intent.setData after the queryIntentActivities makes the intent work
There was an error
Unable to find explicit activity class {com.google.android.apps.translate/com.google.android.apps.translate.TranslateActivity}; have you declared this activity in your AndroidManifest.xml, or does your intent not match its declared <intent-filter>?
In my case help to remove intent.addCategory(Intent.CATEGORY_LAUNCHER) for Android 13
final Intent intent = new Intent();
intent.setAction(Intent.ACTION_PROCESS_TEXT);
intent.setType("text/plain");
//Fix for Android 13
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
intent.addCategory(Intent.CATEGORY_LAUNCHER);
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
intent.setComponent(name);
intent.putExtra(Intent.EXTRA_TEXT, selecteText);
intent.putExtra(Intent.EXTRA_PROCESS_TEXT, selecteText);
intent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, selecteText);
a.startActivity(intent);
If you're running a SDK target below 33 do:
if (Build.VERSION.SDK_INT < 33) {
intent.addCategory(Intent.CATEGORY_LAUNCHER)
}
Add schema to your intent filter in your exported activity.
That help me for SDK 33
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
</intent-filter>
I hava a question about Android Receiver.
I'm possible to change System app.
B is the first app, when user turns on the power. But the problem is when user chooses FACTORY Mode(like setting language, google id...), B App has to be started finishing A App setting. That's why use android:enabled="false"and A App trigger B app. But not working.
I think "android.intent.action.BOOT_COMPLETED" send just one time after booting, so after changing enable receiver B app, it's not working. Is it right?
Please can you give me some advise?
A App
PackageManager pm = getPackageManager();
ComponentName compName = new ComponentName("com.test.myapp", "com.test.myapp.receiver");
pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
B App AndoidManifest.xml
<receiver
android:name="com.test.myapp.receiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
B App
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())){
Intent startMainActivityIntent = new Intent(context, new.class);
startMainActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(startMainActivityIntent);
}
Why don't you just start app B from app A directly? Yes, boot completed triggered only once. But you can start app B without any receivers, look here for example
I'm having a problem starting a service from another Android app (API 17).
However, if I do run 'am' from the shell, the service starts fine.
# am startservice com.xxx.yyy/.SyncService
Starting service: Intent { act=android.intent.action.MAIN cat=
[android.intent.category.LAUNCHER] cmp=com.xxx.yyy/.SyncService }
(service starts fine at this point)
# am to-intent-uri com.xxx.yyy/.SyncService
intent:#Intent;action=android.intent.action.MAIN;
category=android.intent.category.LAUNCHER;
component=com.xxx.yyy/.SyncService;end
So, it doesn't look like I'm missing anything from the intent when I do the same in the code:
Intent i = new Intent();
i.setAction(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_LAUNCHER);
i.setComponent(new ComponentName("com.xxx.yyy", ".SyncService"));
ComponentName c = ctx.startService(i);
if (c == null) { Log.e(TAG, "failed to start with "+i); }
What I get is (the service is not running at that time):
E/tag( 4026): failed to start with Intent {
act=android.intent.action.MAIN
cat=[android.intent.category.LAUNCHER]
cmp=com.xxx.yyy/.SyncService }
I don't have an intent filter on the service, and I don't want to set one up, I'm really trying to understand what am I doing wrong starting it through its component name, or what may be making it impossible to do so.
You should be able to start your service like this:
Intent i = new Intent();
i.setComponent(new ComponentName("com.xxx.yyy", "com.xxx.yyy.SyncService"));
ComponentName c = ctx.startService(i);
You don't need to set ACTION or CATEGORY if you are specifying a specific component. Make sure that your service is properly defined in the manifest.
Start your service like this
Intent intent = new Intent();
intent.setComponent(new ComponentName("pkg", "cls"));
ComponentName c = getApplicationContext().startForegroundService(intent);
btw you actually need to use the applicationId, instead of the pkg. it can be found in the app gradle. I was struggling with that mistake for hours!
defaultConfig {
applicationId "com.xxx.zzz"
}
the cls is the name of your service declared in the manifest. example: com.xxx.yyy.yourService.
<service android:name="com.xxx.yyy.yourService"
android:exported="true"/>
As an addition to David Wasser's answer to make it work when targeting API 30 and above you also have to add either:
Required package name in queries tag in manifest:
<queries>
<package android:name="com.example.service.owner.app" />
</queries>
or permission
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
Additional info on package visibility changes here
I have an implicit intent in a service that sends information to my main activity, as well as to another class. I also now want that intent to launch my main activity. I've looked at the myriad posts related to this, and tried lots of different things--addCategory, setAction(MAIN; the activity's name; you name it, I've tried it...), category.DEFAULT in the manifest, and several other things that either resulted in ActivityNotFoundExceptions (most commonly) or behavior that was otherwise undesirable.
Here's where the intent is set up and the relevant part of the manifest. The receiver for the intent is registered in the main activity.
final String NEW_DOSES = "changed to protect the innocent";
Intent bluetoothBroadcast = new Intent();
several putExtra lines here
bluetoothBroadcast.setAction(NEW_DOSES);
sendBroadcast(bluetoothBroadcast);
<activity
android:name=".AsthmaAppActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Is it possible to get this intent to launch my main activity with relatively minor changes? Thanks in advance.
Yes it is possible but no with sendBroadcast(bluetoothBroadcast); sendBroadcast does not launch an activity. You must use startActivity to achieve this. For example here is what a launcher application will do in order to launch an application:
public static void LaunchApplication(Context cx, String packagename) {
PackageManager pm = cx.getPackageManager();
Intent i = pm.getLaunchIntentForPackage(ai.packageName);
if (i != null) cx.startActivity(i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
You can easily adjust the extras and the data needed in order to launch the activity. For example if your activity is named myActivity then you can go like this:
Intent i = new Intent(cx, myActivity.class);
//Put the extras and the data you want here...
//If you are launching the activity from a receiver component you must use
//i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
cx.startActivity(i);
Hope this helps...
My application is in running mode[foreground] and user clicks on home button, which puts application to background[and still running]. I have alarm functionality in my application which fires up. I want is when my alarm goes off i want to bring my background running application in foreground and from last state in which it was.
<application
android:name="com.abc.android.state.management.MainEPGApp"
android:icon="#drawable/icon"
android:label="#string/app_name"
android:largeHeap="true"
android:logo="#drawable/app_logo" >
<activity
android:name=".SplashScreen"
android:label="#string/app_name"
android:launchMode="singleTop"
android:screenOrientation="nosensor"
android:theme="#style/Theme.Sherlock" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Starter"
android:configChanges="orientation|screenSize"
android:screenOrientation="behind"
android:launchMode="singleTop"
android:uiOptions="none"
android:windowSoftInputMode="adjustPan" />
</application>
Intent intent = new Intent(context, MyRootActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
You should use your starting or root activity for MyRootActivity.
This will bring an existing task to the foreground without actually creating a new Activity. If your application is not running, it will create an instance of MyRootActivity and start it.
EDIT
I added Intent.FLAG_ACTIVITY_SINGLE_TOP to Intent to make it really work!
Second EDIT
There is another way to do this. You can simulate the "launching" of the app the same way that Android launches the app when the user selects it from the list of available apps. If the user starts an application that is already running, Android just brings the existing task to the foreground (which is what you want). Do it like this:
Intent intent = new Intent(context, SplashScreen.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // You need this if starting
// the activity from a service
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
startActivity(intent);
A combination that works for me is to use:
Intent bringToForegroundIntent = new Intent(context, RootActivity.class);
bringToForegroundIntent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(bringToForegroundIntent);
If you check the logs on the device whenever you start an activity from a launcher icon this is the intent that gets passed to launch the app or move it back to foreground if e.g. user clicked the Home button.
I find a new way to bring app to foreground,
It imitate click Icon to launch application.
PackageManager packageManager = context.getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage(pkName);
if (intent != null)
{
//模拟点击桌面图标的启动参数
intent.setPackage(null);
// intent.setSourceBounds(new Rect(804,378, 1068, 657));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
context.startActivity(intent);
}
it work for me, But who can tell me why package must be null
You can use the below code to bring the application to front:
private void bringApplicationToFront()
{
Log.d(TAG, "====Bringging Application to Front====");
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
try
{
pendingIntent.send();
}
catch (CanceledException e)
{
e.printStackTrace();
}
}
From my test, to mimic the Launcher behavior, the key is to:
intent.setPackage(null);
after
Intent intent = packageManager.getLaunchIntentForPackage(pkName);
Other methods in this thread doesn't work in my case.
Thanks to jiaqing's answer, I post this answer as I don't have the right to comment. I don't know the logic behind this either, I guess it's related with the task owner. Anyone knows, would be glad to know.
Using the ActivityManager class you can bring a running task to front
void bringToFront(){
ActivityManager activtyManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> runningTaskInfos = activtyManager.getRunningTasks(3);
for (ActivityManager.RunningTaskInfo runningTaskInfo : runningTaskInfos)
{
if (this.getPackageName().equals(runningTaskInfo.topActivity.getPackageName()))
{
activtyManager.moveTaskToFront(runningTaskInfo.id, ActivityManager.MOVE_TASK_WITH_HOME);
return;
}
}
}
For an alarm, you want to take a look at starting an Android Service
This service will be more resilient than your application which may be killed while in the background and can fire off an intent to bring your application to the front (or restart it if it was killed) when it is time for the alarm to go off.