Without using an Intent and passing an extra parameter, is it possible to determine which Activity launched the current Activity?
There are two approaches to do.
the common one
use startActivityForResult in your calling activity to start the activity. Here I use ActivityCompat for backward compatibility.
ActivityCompat.startActivityForResult(this, new Intent(this, MyActivity.class), 0, null);
Then in the callee activity, you can use the following code to detect the calling activity.
if (getCallingActivity() != null) {
Log.d(TAG, getCallingActivity().getClassName());
}
NOTE
In the calling activity, you don't have to implement onActivityResult if you don't need transfer data from callee activity.
the deprecated one
use the system's back stack to get the recent tasks. But it won't work after API 21, it's deprecated after API 21.
declare the necessary permission in the mainifest.
<uses-permission android:name="android.permission.GET_TASKS" />
Then in your callee activity
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
for (ActivityManager.RecentTaskInfo taskInfo : activityManager.getRecentTasks(1, 0)) {
Log.d(TAG, taskInfo.baseIntent.getComponent().getClassName());
}
Only thing that crosses my mind is a HashMap with "lastActivity" "Activity" key and value.
It would be static so you can update it on every Activity incantation.
Or to save it as a SharedPreferences but that is the same as HashMap ...
Hope it helped, best regards!
You Application class allows setting an ActivityLifecycleListener. Whenever a new activity touches onCreate() you can push the stuff like activities toString() or getClass().getSimpleName() on a stack of Strings, then use it to identify it.
Related
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.
I will explain my app first. My app consists of a service running in the background. The service is waiting for a certain app to be opened (spotify in my case).
Whenever Spotify opens it needs to show this popup with buttons and text. It has to basicly look like this: imgur.com/QHWZpu6
I've already tried DialogFragment but that doesn't work since there is no FragmentManager in the service class.
Do you guys have any suggestions? Thanks in advance!
Detect if App is Running/Launched
Take a look at ActivityManager
The method you are concerned with is ActivityManager.RunningAppProcessInfo(). It returns the package names of current running apps as a list. All you have to do is iterate through list and check if it matches app package, which in your case is spotify.
ActivityManager activityManager = (ActivityManager) this.getSystemService( ACTIVITY_SERVICE );
List<RunningAppProcessInfo> appInfos = activityManager.getRunningAppProcesses();
for(int i = 0; i < appInfos.size(); i++)
{
if(appInfos.get(i).processName.equals("package name"))
{
//USE INTENT TO START YOUR ACTIVITY AS EXPLAINED BELOW
}
}
Start Activity from Service
To start activity from your service, use Intent as following:
Intent popUpIntent = new Intent(this, MyActivity.class);
popUpIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(popUpIntent);
Popup Activity / Show as Dialog
In order to display your Activity as a dialog, you just need to set your activity theme to "Theme.Dialog" in your manifest file
<activity
...
android:theme="#android:style/Theme.Dialog"
...
/>
Or you can dynamically set the theme in the activity by calling setTheme() inside Activity class.
setTheme(android.R.style.Theme_Dialog);
A service class is not supposed to interact with the UI thread. A simple interaction to get past this is to use the service thread to update something stored in SharedPreferences and set a shared preferences change listener in the UI thread for whichever activity you want to handle the UI display.
void onSharedPreferenceChanged (SharedPreferences sharedPreferences,
String key)
Based on that image, it seems you actually want to display a UI element when you're activity is not currently the active application, you might want to consider a notification for this instead
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
I know that Activities can be declared in manifest as being excluded from recents with android:excludeFromRecents:
http://developer.android.com/guide/topics/manifest/activity-element.html#exclude
However, that's not what I'm looking for, I would like to know if there is a way to remove the app from recent apps programmatically
Yes, generally when you want to have special properties for an Activity when starting it you supply special flags to the Intent. In this case FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS.
Updated:
If you need to hide the current already running activity, you might be able to use this flag in combination with FLAG_ACTIVITY_CLEAR_TOP which would send the new Intent to the existing Activity. You'll have to think and perhaps experiment with what happens as the user moves around your stack though and whether that will make your app re-appear in the recent apps.
This can be done using the ActivityManager.AppTask functionality (starting in API 21)
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
if(am != null) {
List<ActivityManager.AppTask> tasks = am.getAppTasks();
if (tasks != null && tasks.size() > 0) {
tasks.get(0).setExcludeFromRecents(true);
}
}
Add these lines to the Activity from which you are exiting the application:
#Override
public void finish() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
super.finishAndRemoveTask();
}
else {
super.finish();
}
}
Following is the definition of the flag android:excludeFromRecents (which i know you have already seen):
Whether or not the task initiated by this activity should be excluded from the list of recently used applications ("recent apps").
That is, when this activity is the root activity of a new task, this
attribute determines whether the task should not appear in the list of
recent apps. "true" if the task should be excluded from the list;
"false" if it should be included. The default value is "false".
so to remove the app from the list of recent app you can set this flag on the first activity in your application since that activity launches the the task for you application. If you have multiple tasks (unlikely for most apps) in your application then you need o set this flag for root activity of all the task.
Call this when your activity is done and should be closed and the task should be completely removed as a part of finishing the root activity of the task.
finishAndRemoveTask();
After receiving the other answers, I managed to get to the following workaround: I have an Activity, let's call it XPTO, declared in manifest with
and basically, when I want app to disappear from the recents list, I finish all other activities, then start the XPTO, which basically kills the app (calling android.os.Process.killProcess(android.os.Process.myPid()); in its onResume()
If anyone has a better way of doing this, please share it
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)