I am new to android development. I've been trying to open an app from a JobService that is scheduled from the onReceive() method of an implicit broadcast-receiver, declared in my manifest.
From some posts, I found that one can use the packageManager.getLaunchIntentForPackage() method and then use it to launch said package by starting a new activty with context.startActivity(), so I declared the function below:
fun openApp(packageName: String, context: Context){
val startIntent: Intent? =
context.packageManager.getLaunchIntentForPackage(packageName)
startIntent?.addFlags(
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or
Intent.FLAG_ACTIVITY_NEW_TASK
);
context.startActivity(startIntent)
}
When used on the MainActivity file, this function works as expected, but when I try using it on my JobService, I noticed that it only works if my app has the Display Over Other Apps permission.
Is this to be expected? Is there any way to contourn this requirement? Is this because of the way I am passing the context in my openApp() function?
Thx in advance!
Thanks to CommonsWare's comments, I've found the documentation I needed:
Restrictions on starting activities from the background.
In my use case, I needed to cancel a call to a specific number and open my app instead, so I adapted my code to take advantage of the CallRedirectionService class, which is treated as an exception and therefore can start activities from the background.
Related
I have put in place the following:
I added <receiver android:name="BootReceiver"></receiver> to application in the manifest XML file.
I added <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> to the manifest also.
I created a new class in the droid project called BootReceiver:
using Android.App;
using Android.Content;
using uarapp.droid;
[BroadcastReceiver(Enabled = true, DirectBootAware = true, Exported = true)]
[IntentFilter(new[] { Intent.ActionBootCompleted }, Priority = (int)IntentFilterPriority.HighPriority)]
public class BootReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
Intent i = new Intent(context, typeof(MainActivity));
i.AddFlags(ActivityFlags.NewTask);
context.StartActivity(i);
}
}
The app does not start when the device boots. From Googling it looks like this process has changed for latest version of Android. Anybody know what needs to change? I can't find it online anywhere.
In case it matters the specific device I'm targetting is a RealWear HMT-1.
A lot has changed with Android 10 there are a lot of restrictions on starting something in the background more information here.
The possible solution is in the following answer on SO: https://stackoverflow.com/a/59421118/7462031
Possible Solutions:
1- You can choose just show a service notification, and start pending intent with a click
2- You can use full-screen intents to show your intent immediately as shown in the other answer and suggested by Google.
For full-screen intent solution, as described in the official document
The system UI may choose to display a heads-up notification, instead of launching this intent, while the user is using the device.
3- To start the activity automatically in the background, The most possible solution in my view is adding "SYSTEM_ALERT_WINDOW" to the manifest file. And ask for user permission once when the app opened the first time. (The user can give this permission manually - (Settings-Apps-Your App-Advanced- Draw over other apps))
I have also read about some brands restricting the above solutions so you might wanna investigate that too.
Good luck feel free to get back if you have queries.
According to new behavior documented here https://developer.android.com/about/versions/pie/android-9.0-changes-all#fant-required starting activities from non-activity context requires FLAG_ACTIVITY_NEW_TASK flag, perhaps...
So I have created sandbox app that can launch activity of another apps by package name and activity name. The core function used to launching, looks like this:
fun Context.startActivity(packageName: String, activityName: String) {
applicationContext.startActivity(Intent(Intent.ACTION_MAIN).apply {
component = ComponentName(packageName, activityName)
})
}
What is odd, by calling this function I can succesfully start any exported activity without passing FLAG_ACTIVITY_NEW_TASK.
It is quite different from what google saying about that. Or maybe I wrongly understand this new behavior requirements?
Off course I tested this on API 28 but also on lower API's.
Can someone explain in which cases this new behavior can breaks any feature that works on older APIs?
As per this change it seems to be a restriction when FLAG_ACTIVITY_NEW_TASK is not set while launching an activity from a non-activity context.
StartActivity method in Activity.java is overriden and avoid this restriction.
Everyone knows if you create intent to start another activity, you pass in as a parameter into startActivity. But I just thought about the possible scenario: intent says system "call this activity", the system sees manifest and then runs activity, or this running acts internally in the app, something like "call some method of some class"?
Probably a stupid question, but I couldn't find enough info. So how does it works?
Following is the way how intent communication works:
Activity A creates an Intent with an action description and passes it to startActivity().
The Android System searches all apps for an intent filter that matches the intent. When a match is found,
the system starts the matching activity (Activity B) by invoking its onCreate() method and passing it the Intent.!
I have a UI test which clicks a button, and then launch a new Activity in its onClickListener. The test checks whether expected intent is sent or not.
My problem is, I want to test whether expected intent is sent without actually launching the activity. Because I found that new activity initializes its state, and it makes subsequent tests flaky.
I know there are two Espresso Intents API, which are intended and intending, but both fail to meet my needs. intended API actually launches the target activity, and intending API doesn't launch the activity, but it calls onActivityResult callback which I don't want either. Because I'm afraid that code inside onActivityResult may cause another flakiness.
Also intending doesn't assert whether matching intent is sent. It just calls onActivityResult callback when matching intent is found, which means I have to check whether onActivityResult is called or not!
Is there a clean way to achieve what I want?
If you want to test whether expected intent is sent without actually launching the activity you can do it by capturing the intent with an activityResult and then catching the activity :
Intent intent = new Intent();
ActivityResult intentResult = new ActivityResult(Activity.RESULT_OK,intent);
intending(anyIntent()).respondWith(intentResult);
onView(withId(R.id.view_id_to_perform_clicking)).check(matches(isDisplayed())).perform(click());
intended(allOf(hasComponent(ActivityToBeOpened.class.getName())));
This would catch any attempt of launching ActivityToBeOpened. If you want to be more specific you can also catch an intent with Extras:
intended(allOf(hasComponent(ActivityToBeOpened.class.getName()), hasExtra("paramName", "value")));
Hope that helps.
Espresso's Intents class is a concise and handy api, but when it doesn't meet your needs, there is an alternative. If you use AndroidJUnit4 test runner, you can get Instrumentaion instance using InstrumentationRegistry.getInstrumentation(), and then you can add Instrumentation.ActivityMonitor instance.
Instrumentation.ActivityMonitor am = new Instrumentation.ActivityMonitor("YOUR_ACTIVITY", null, true);
InstrumentationRegistry.getInstrumentation().addMonitor(am);
onView(withId(R.id.view_id_to_perform_clicking)).check(matches(isDisplayed())).perform(click());
assertTrue(InstrumentationRegistry.getInstrumentation().checkMonitorHit(am, 1));
The third parameter of ActivityMonitor constructor tells we want to block activity launching. Note that this approach has its limitation. In contrast to Espresso Intents' rich Matcher support, You can not set multiple condition for ActivityMonitor.
You can find several samples in ApiDemos, especially in ContactsSelectInstrumentation class.
Actually, you can block any intent to launch an external or your own activity but still use the rich Espresso Intents API:
Instrumentation.ActivityMonitor soloMonitor = solo.getActivityMonitor();
instrumentation.removeMonitor(soloMonitor);
IntentFilter filter = null;
// Block any intent
Instrumentation.ActivityMonitor monitor = instrumentation.addMonitor(filter, null, true);
instrumentation.addMonitor(soloMonitor);
// User action that results in an external browser activity being launched.
user.clickOnView(system.getView(R.id.callButton));
instrumentation.waitForIdleSync();
Intents.intended(Matchers.allOf(
IntentMatchers.hasAction(Matchers.equalTo(Intent.ACTION_VIEW)),
IntentMatchers.hasData(Matchers.equalTo(Uri.parse(url))),
IntentMatchers.toPackage(chromePackage)));
instrumentation.removeMonitor(monitor);
You able to do that because Espresso Intents still records every Intent with IntentMonitor callback even if you block them. Look at the source code of Espresso Intents on how they do that.
If you use Robotium Solo framework you need to move your own ActivityMonitor before their one. Otherwise just skip the lines related to this.
I want to start multiple instance of the same Activity class from a Service. The reason I'm doing this, is because I have a Service that runs a "scan" daily, and if it finds any malfunctions it should display a popup for each malfunction.
The Activity that I'm starting is more like a Dialog, has a Dialog theme to display info about the malfunction.
Manfiest:
<activity
android:name=".ui.dialogs.MalfunctionActivity"
android:theme="#style/MyDialog"
android:launchMode="standard">
Intent to start the activity from Service:
Intent displayMalf=new Intent(this, MalfunctionActivity.class);
displayMalf.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(displayMalf);
PROBLEM: to start the Activity from a Service I need the FLAG_ACTIVITY_NEW_TASK which somehow cancels the launchMode="standard" from the manifest, and gives me just one Activity even if I try to start multiple diffrent instances.
Is there anyway in which I can achieve this?
It was so simple. There is the flag FLAG_ACTIVITY_MULTIPLE_TASK which according to the documentation :
Used in conjunction with FLAG_ACTIVITY_NEW_TASK to disable the behavior of bringing an existing task to the foreground. When set, a new task is always started to host the Activity for the Intent, regardless of whether there is already an existing task running the same thing.
Was exactly what I need. Thanks and sorry for answering on my question. It is not a habit. :)
Service will take the flag FLAG_ACTIVITY_NEW_TASK to start the activity but here you can try like this:
Set the instance of the handler of the activity of which you want multiple instances, in the service.
When you want the new instance of the activity use handler.sendMessage(msg) and on receiving this msg in your activity, start this activity again.
I guess your app works in the background and will display the popups even if the app is not in the foreground at the moment, right?
Otherwise I would use normal popup's (AlertViews) instead of starting new activities all the time.
If the app works in the background, you could tell the user with the first popup that your app has found one or more malfunctions and that he should activate the app for more details