When launching an Activity for an app, the first piece of my code that runs is my subclass of Application.onCreate(). Is there a way to know which Activity triggered that?
More specifically, in my Application subclass onCreate(), I do some database initialization. This can fail and my general solution for failures is to launch another activity where I can display something to the user. This works fine if the failure is anywhere but in Application.onCreate().
When the failure is in Application.onCreate(), Android tries to restart my Application subclass, which in turn fails, and so on. I can prevent the infinite loop with the activity SingleInstance attribute. But that prevents any activity from starting up.
One solution would be to move my database code into my main activity's onStart(). However, I would prefer to leave it in Application.onCreate() if there's a way I can bypass it when the error handling activity is trying to launch.
One approach would be to switch to ACRA for your exception-handling activity, or at least to use their technique.
ACRA winds up in a separate :acra process. They then use ActivityManager and getRunningAppProcesses() to determine if the current process is the :acra process or not:
/**
* #return true if the current process is the process running the SenderService.
* NB this assumes that your SenderService is configured to used the default ':acra' process.
*/
public static boolean isACRASenderServiceProcess(#NonNull Application app) {
final String processName = getCurrentProcessName(app);
if (ACRA.DEV_LOGGING) log.d(LOG_TAG, "ACRA processName='" + processName + "'");
return (processName != null) && processName.equals(ACRA_PRIVATE_PROCESS_NAME);
}
#Nullable
private static String getCurrentProcessName(#NonNull Application app) {
final int processId = android.os.Process.myPid();
final ActivityManager manager = (ActivityManager) app.getSystemService(Context.ACTIVITY_SERVICE);
for (final ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()){
if(processInfo.pid == processId){
return processInfo.processName;
}
}
return null;
}
While getRunningAppProcesses() has been lobotomized in Android 5.0+, you can still use it for your own processes, which is all that we need here.
Given that you know whether you are in the ACRA process or not, you can decide whether or not to do certain initialization, such as your database initialization.
In your case, you would isolate the exception-handling activity in a separate named process, see if you are in that process in Application#onCreate(), and skip the database initialization if you are.
If I understand you correctly, you want to know, WHO started your activity.
And if I also understand correctly, you do start (at least sometimes) this activity from inside your app.
If both assumptions are true, take a look at the Intent class. You start an activity with an intent, where you can put anything in it, with methods like .putString(...) and similar.
So when starting your activity do something like
Intent intent = new Intent(this, myotheractivity.class);
intent.putString("caller", this.getClass().getSimpleName());
startActivity(intent);
And store the name of the calling class (or anything else!) in the activity.
In the onCreate() or your activity just check with a construct like this:
Intent intent = getIntent();
if (intent != null) {
String caller = intent.getString("caller", "");
if (!caller.equals("")) {
// Here caller contains the name of the calling class
}
}
If this intent is null or caller=="", it was not your own app that started this activity.
Cheers
Related
I am attempting to instrumentation test an Activity in isolation however I'm running into issues because part of the testing requires that I verify that the Activity under test launches another Activity via an Intent.
What I'm looking for is some way to intercept an Intent so that I can verify that the isolated Activity actually attempted to launch the next Activity but without the next Activity actually launching.
The issue I'm running into is that when the next Activity launches it crashes because I'm unable to mock a few critical things that it requires. It would be perfect if there was a way to intercept the Intent during testing so that the next Activity never launches.
Is what I'm looking for even possible?
Originally I tried to use Espresso's intended() and intending() methods in order to verify that Intents were being sent without actually starting an Activity (as described here: https://collectiveidea.com/blog/archives/2015/08/11/stub-your-android-intents
However I did not have luck making that work. What I eventually resorted to was using ActivityMonitor to do the job.
Here's an example:
private void registerActivityMonitorAndStartActivity(String name) {
Instrumentation.ActivityMonitor am = new
Instrumentation.ActivityMonitor(name, null, true);
InstrumentationRegistry.getInstrumentation().addMonitor(am);
mActivityTestRule.launchActivity(new Intent());
int count = 0;
while(!InstrumentationRegistry.getInstrumentation().checkMonitorHit(am, 1) && count < 50000) {
count++;
}
Timber.d("Count = " + String.valueOf(count));
assertTrue(InstrumentationRegistry.getInstrumentation().checkMonitorHit(am, 1));
}
This basically has an activity monitor watch for an intent sent to an activity that you specify by name. A while loop runs until the activity monitor sees a hit and then breaks or breaks if a timeout is hit.
Thanks in advance for the help.
I have an app that can be started by either the user physically starting the app (like you would any normal app) or by a repeating service. Depending on what starts the app (the user or the service) I want to preform different initialization actions. How might I be able to detect if an user starts the app without doing anything custom (I imagine that there has to be some kind of built in setting in android for me to determine this)?
If service, that starts your Activity, is yours service, you can put some custom information (using Intent#putExtra for example) in Intent you use to start Activity from Service.
In Activity you can use Activity#getIntent(), that returns the intent that started this activity.
If you started Activity from Service, that Intent will be the one you passed in Service#startActivity, and will have your custom information. Otherwise, that was not your Service, that started your Activity.
That could look somehow like that, for example:
//in Activity
public static final String EXTRA_STARTED_FROM_MY_SERVICE = "com.example.extra_started_from_sevice";
private boolean wasActivityStartedFromService() {
Intent startingIntent = getIntent();
//assuming you will use Intent#putExtra in your service when starting activity
return startingIntent.getBooleanExtra(EXTRA_STARTED_FROM_MY_SERVICE, false);
}
//...
//in Service
//...
Intent startingIntent = new Intent(this, MainActivity.class);
startingIntent.putExtra(MainActivity.EXTRA_STARTED_FROM_MY_SERVICE, true);
startActivity(startingIntent);
In my app, I want to programmatically bring the most recently used third party Activity to the front. After looking at other answers here, I've tried relaunching the activity using the baseIntent returned from a list of recent tasks, but that does not seem to bring the activity to the front over whatever else is going on.
My end goal is to create an app that replaces the incoming call screen with a small overlay so the user is not pulled completely out of whatever app they are using when they get a call. I've found you can't replace the default incoming call screen (if this is not true, please let me know as I'd rather do that instead) so as a workaround, I am trying to call the most recently used app to the front of the screen (to overlay the incoming call screen) and then display my overlay on top of that.
Here's the code I am using (The activity is launched from a broadcast receiver)
public class BringMRUAppToFront extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
ActivityManager activityManager = (ActivityManager) getSystemService("activity");
List<RecentTaskInfo> recentTasks = activityManager.getRecentTasks(1, ActivityManager.RECENT_WITH_EXCLUDED);
if(recentTasks.size() > 2) {
RecentTaskInfo recentTask = recentTasks.get(2);
Intent testIntent = recentTask.baseIntent;
Log.i("MyApp", "Recent task - " + recentTask.baseIntent.getComponent().getPackageName());
testIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(testIntent);
}
finish();
}
}
Is there a reliable way to bring any third party activity to the front? The activity is guaranteed to be in memory (if one is not in memory, then I would just display the home screen), so there shouldn't be an issue there. I also don't believe it would be a security issue in this case as it would just be displaying an app that was visible right before the phone rang - though I do understand that opening this up in general in the SDK could pose a risk...still hoping it is possible.
EDIT: Modified the code slightly to reflect what I'm doing. The desired task will almost always be the 3rd task in the list - first is the current task and second is the task of the ringing phone. I am able to call the task to the front, but it is not always in the same state (going to the browser's page instead of the settings screen in the browser, for example). How does the recent tasks list do this?
Figured it out by looking at the Android source code - this is exactly what the Recent Tasks screen does, both pre- and post-Honeycomb:
RecentTaskInfo recentTask = recentTasks.get(1); // task 0 is current task
// maintains state more accurately - only available in API 11+ (3.0/Honeycomb+)
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
am.moveTaskToFront(recentTask.id, ActivityManager.MOVE_TASK_WITH_HOME);
} else { // API 10 and below (Gingerbread on down)
Intent restartTaskIntent = new Intent(recentTask.baseIntent);
if (restartTaskIntent != null) {
restartTaskIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
try {
startActivity(restartTaskIntent);
} catch (ActivityNotFoundException e) {
Log.w("MyApp", "Unable to launch recent task", e);
}
}
}
Permission android.permission.REORDER_TASKS is necessary for this to work correctly.
Start from Android L, getRecentTasks() returns application’s own tasks and possibly some other non-sensitive tasks (such as Home), so you can not do such job.
References: https://developer.android.com/preview/api-overview.html#Behaviors
Why dont you use a global static variable that is accessed by all activities and on its onPause just set that variable to that activity value
Eg
I am placing a static variable called ActivityName
public static String ActivityName = "";
and in the onPause of every activity just assign it the activities pacakage name
#Override
public void onPause() {
super.onPause();
// The static global variable
com.pacakagename.Utls.ActivityName = "com.pacakagename.Act1";
}
So when any of the activity pauses the value is assinged and you will come to know of the most recent paused activity and you can restart it using Class.forName(ActivityName)
I have an annoying issue with the foreground dispatch behavior. Sometimes instead of calling onNewIntent(), it completely recreates the activity, which breaks the app's workflow.
My concrete situation: Activity A is the MainActivity, which uses the foreground dispatch. Everything works as it should. However, in my activity B, which is launched from the browser (VIEW action), the foreground dispatch doesn't work under some circumstances anymore.
The workflow:
I start the MainActivity, switch to the browser (without closing
the MainActivity), launch activity B and attach my NFC device --> it
creates a new activity B.
I start the MainActivity and close it again. After that I switch
to the browser, launch activity B and attach my NFC device -->
everything works with onNewIntent()
The code is correct, e.g. if I attach the NFC device in the first scenario twice, it works as it should at the second time, but not at the first time. In the MainActivity and activity B I definitively call the disableForegroundDispatch() method in the activity's onPause() method.
Is there a solution for my specific problem? For me it sounds like a bug.
Edit:
public void resume(Activity targetActivity) {
if (nfc != null && nfc.isEnabled()) {
// nfc is the default NFC adapter and never null on my devices
Intent intent = new Intent(targetActivity, targetActivity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(targetActivity, 0, intent, 0);
nfc.enableForegroundDispatch(targetActivity, pendingIntent, null, new String[][] { new String[] { IsoDep.class.getName() } });
}
}
public void pause(Activity targetActivity) {
if (nfc != null && nfc.isEnabled()) {
nfc.disableForegroundDispatch(targetActivity);
}
}
These methods are called in the corresponding methods in each activity. Thanks for the help!
Solution: After a very long research I finally found the issue. Logcat printed:
startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: Intent
I found other issues at Stackoverflow, where people had have the same issue with the NotificationManager, but all the hints didn't help me. Adding the flag singleTask to my activity B did the trick for me, but to be honest I don't understand it, because the context is always an activity.
I removed all the code from the MainActivity and the first scenario still didn't work. I romved the MainActivity from the manifest and after that everything was fine. Maybe it is a problem, that an app instance is running and activity B is launched from the browser? I don't know.
Anyway, thanks for the help NFC guy!
The IntentFilter[] that you pass to enableForegroundDispatch is empty. So your NFC intent probably arrive at your Activity due to the IntentFilter(s) in the manifest file. This explains the behaviour you observe, as an NFC intent always creates a new instance when delivered this way.
Add something like this instead to your code for enabling foreground dispatch:
IntentFilter[] iFilters = new IntentFilter[2];
iFilters[0] = new IntentFilter();
iFilters[0].addAction("android.nfc.action.TECH_DISCOVERED");
iFilters[1] = new IntentFilter();
iFilters[1].addAction("android.nfc.action.TAG_DISCOVERED");
iFilters[1].addCategory(Intent.CATEGORY_DEFAULT);
And pass that as parameter to enableForegroundDispatch.
UPDATE:
I recently learned more about this specific problem. It is caused by the way Android determines in which task a new Activity should be launched. I don't know or understand the specific details of how that works, but the effect is that:
When Activity B is launched from the Browser, it is created in the Browser's task
When the NFC intent arrives, the system determines that a new Activity B is to be created in Activity A's task
Because of 2., the SINGLE_TOP is not ignored: there is only one instance of Activity B at the top of A's task. When Activity A is closed, it's task has disappeared, so Activity B will always be created in the Browser's task, as you have observed.
You may feel that this is an Android bug in this case (I do, I think), but this behaviour of how to create activities in which task is so fundamental to Android that many apps rely on it. So it is very unlikely that this will ever change.
Possible work-around: declare Activity B with launchMode "singleTask" (or "singleInstance"). Then a new (3rd) task will be created when B is launched.
I guess your workflow is as follow : Main --> Detect tag --> Reader Activity --> Writer activity Detect tag --> Write tag
This problem seems to arise when your writer activity (the one with foreground enabled, I suppose that's for writing purposes) belongs to an Activity Stack (e.g. a Task) that was called from a previous tag discovery.
In particular, it doesn't arise if your workflow is as follow :
Main Writer activity Detect tag --> Write tag
My workaround is to call the writer activity in a new task to begin with.
In the activity that laucnhes the writer, just add the new task flag in the intent that starts the writer.
startActivity(new Intent(this,MyTagWriterActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Main --> Detect tag --> Reader Activity -NEW_TASK-> Writer activity Detect tag --> Write tag
It does mess with the activity history, but makes the writer activity more predictable.
Here is my problem -
I copied my .apk file onto phone memory card and launch my application clicking on it and it allows me to install my application.I install my application.Finally,I got system installation pop up containing two options "Open" and "Done".When i click "Open" my application got launched.Up to this point everything is working without any problem.
Now in my application I click on a button and some download is taking place as a result(Showing progress dialog).Now I press a Home button,so my application goes to background.
Now I again launch my application by going inside Menu and clicking on my application icon.
Expected result - Still I Should see Progress Dialog for downloading.
Actual result - A new instance/session of my application is getting started.
So how to avoid this so that only one and one instance/session of my application should run.
#Palejandro, here you are. Put the code below into your main activity onCreate() method:
// Possible work around for market launches. See
// http://code.google.com/p/android/issues/detail?id=2373
// for more details. Essentially, the market launches the main activity
// on top of other activities.
// We never want this to happen. Instead, we check if we are the root
// and if not, we finish.
if (!isTaskRoot()) {
final Intent intent = getIntent();
final String intentAction = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) {
Log.w(TAG, "Main Activity is not the root. Finishing Main Activity instead of launching.");
finish();
return;
}
}
I used this piece of code in my projects and it works fine!
I believe you need to put
<activity
android:launchMode="singleInstance"
</activity>
in the manifest file.
what do your OnPause, OnResume and OnCreate?
I will bet money you are not saving anything OnPause, and starting a new instance all the time via OnCreate.
You should read the notes on Activity Lifecycles.
If you haven't got this sorted yet, I would say your app is actually being killed when home is pressed, or perhaps you have a bug that doesn't latch onto whatever object is keeping state.
// put below code in your launcher activity before call super and setcontentview()
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
// get the info from the currently running task
List< ActivityManager.RunningTaskInfo > taskInfo = am.getRunningTasks(10);
boolean alreadyTask=false;
for(ActivityManager.RunningTaskInfo info : taskInfo){
ComponentName componentInfo = info.topActivity;
String value= componentInfo.getPackageName();
if(value.contains(getPackageName()) && !info.topActivity.getClassName().contains(getPackageName()+".LauncherActivity")){
alreadyTask=true;
Log.i(TAG, "second instance found!!!");
break;
}
}
if(alreadyTask){
finish();
}
I don't have a solution but the problem is that the intent used to start the app is different when you open it directly from install compared to opening it from your home screen. Since it will get started by two different intents it will open a new instance the second time round.
A quick work around is to avoid pressing "Open" when you have installed the application. Press "Done" and then find the application yourself.
See: http://code.google.com/p/android/issues/detail?id=2373