Android: How can I get the current foreground activity (from a service)?
Asked Answered
K

13

200

Is there a native android way to get a reference to the currently running Activity from a service?

I have a service running on the background, and I would like to update my current Activity when an event occurs (in the service). Is there a easy way to do that (like the one I suggested above)?

Kiyohara answered 6/10, 2010 at 14:39 Comment(1)
maybe this can give you an idea https://mcmap.net/q/89538/-how-to-get-current-foreground-activity-context-in-androidChagrin
T
88

Is there a native android way to get a reference to the currently running Activity from a service?

You may not own the "currently running Activity".

I have a service running on the background, and I would like to update my current Activity when an event occurs (in the service). Is there a easy way to do that (like the one I suggested above)?

  1. Send a broadcast Intent to the activity -- here is a sample project demonstrating this pattern
  2. Have the activity supply a PendingIntent (e.g., via createPendingResult()) that the service invokes
  3. Have the activity register a callback or listener object with the service via bindService(), and have the service call an event method on that callback/listener object
  4. Send an ordered broadcast Intent to the activity, with a low-priority BroadcastReceiver as backup (to raise a Notification if the activity is not on-screen) -- here is a blog post with more on this pattern
Tatianna answered 6/10, 2010 at 15:6 Comment(5)
Thanks, but as I do have a looot of activities, and don't want to update them all, I was looking for an android way to get the foregroundActivity. As you say, I should not be able to do that. Means I have to get my way around this.Kiyohara
@George: What you want wouldn't help you, anyway. As you point out, you have "a looot of activities". Hence, those are all separate classes. Hence, there is nothing your service can do with them without either a massive if-block of instanceof checks or refactoring the activities to share a common superclass or interface. And if you're going to refactor the activities, you may as well do it in a way that fits better with the framework and covers more scenarios, such as none of your activities being active. #4 is probably the least work and most flexible.Tatianna
Thanks, but I have a better solution. All my activities extends a customised BaseActivity class. I have set a ContextRegister that registeres the activity as current whenever it is on the foreground, with literraly 3 lines in my BaseActivity class. Still, thanks for the support.Kiyohara
@George: Watch out for memory leaks. Mutable static data members are to be avoided in Java wherever possible.Tatianna
I have tightly encapsualted the object, though I will indeed keep this in mind. Thanks.Kiyohara
L
149

Update: this no longer works with other apps' activities as of Android 5.0


Here's a good way to do it using the activity manager. You basically get the runningTasks from the activity manager. It will always return the currently active task first. From there you can get the topActivity.

Example here

There's an easy way of getting a list of running tasks from the ActivityManager service. You can request a maximum number of tasks running on the phone, and by default, the currently active task is returned first.

Once you have that you can get a ComponentName object by requesting the topActivity from your list.

Here's an example.

    ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
    Log.d("topActivity", "CURRENT Activity ::" + taskInfo.get(0).topActivity.getClassName());
    ComponentName componentInfo = taskInfo.get(0).topActivity;
    componentInfo.getPackageName();

You will need the following permission on your manifest:

<uses-permission android:name="android.permission.GET_TASKS"/>
Lundt answered 20/1, 2011 at 22:20 Comment(11)
Your answer is 100% correct.Thanxx 4 it.Its better that u post the same answer hereShredding
Will this work even with the ability for multiple fragments to present at once?Spree
@Spree haven't verified but yes, the number of fragments you have is irrelevant as they are always hosted by a single activity.Lundt
If i need the current activity more frequently, I have to poll very frequently right? Is there a way to get a callback event whenever the foreground activity changes?Endocrine
Just so everybody is aware, the docs state this about the getRunningTasks() method. -------- Note: this method is only intended for debugging and presenting task management user interfaces. This should never be used for core logic in an application, such as deciding between different behaviors based on the information found here. Such uses are not supported, and will likely break in the future. For example, if multiple applications can be actively running at the same time, assumptions made about the meaning of the data here for purposes of control flow will be incorrect. ------------Paleoecology
@Ryan Is there better solution to get current foreground activity now? Thanks!Leesa
semifake on api 21 As of LOLLIPOP, this method is no longer available to third party applications: the introduction of document-centric recents means it can leak person information to the caller. For backwards compatibility, it will still return a small subset of its data: at least the caller's own tasks, and possibly some other tasks such as home that are known to not be sensitive.Rapparee
How to get the Activity object by getting componentInfo.getPackageName();? In case I want to set the current Activity brightness in background Service.Caducity
@PeterZhu, if you want to get the instance of Activity, then I don't think that's possible for security reasons. I don't know what you mean about setting activity brightness; it might be worth creating a new StackOverflow question for that.Alienation
Is there any way we can get the top activity in Lollipop ?Sparse
@aka_007, yes; use an AccessibilityService.Alienation
A
130

Warning: Google Play violation

Google has threatened to remove apps from the Play Store if they use accessibility services for non-accessibility purposes. However, this is reportedly being reconsidered.


Use an AccessibilityService

Benefits

  • Tested and working in Android 2.2 (API 8) through Android 7.1 (API 25).
  • Doesn't require polling.
  • Doesn't require the GET_TASKS permission.

Disadvantages

  • Each user must enable the service in Android's accessibility settings.
  • This isn't 100% reliable. Occasionally the events come in out-of-order.
  • The service is always running.
  • When a user tries to enable the AccessibilityService, they can't press the OK button if an app has placed an overlay on the screen. Some apps that do this are Velis Auto Brightness and Lux. This can be confusing because the user might not know why they can't press the button or how to work around it.
  • The AccessibilityService won't know the current activity until the first change of activity.

Example

Service

public class WindowChangeDetectingService extends AccessibilityService {

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();

        //Configure these here for compatibility with API 13 and below.
        AccessibilityServiceInfo config = new AccessibilityServiceInfo();
        config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
        config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;

        if (Build.VERSION.SDK_INT >= 16)
            //Just in case this helps
            config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;

        setServiceInfo(config);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if (event.getPackageName() != null && event.getClassName() != null) {
                ComponentName componentName = new ComponentName(
                    event.getPackageName().toString(),
                    event.getClassName().toString()
                );

                ActivityInfo activityInfo = tryGetActivity(componentName);
                boolean isActivity = activityInfo != null;
                if (isActivity)
                    Log.i("CurrentActivity", componentName.flattenToShortString());
            }
        }
    }

    private ActivityInfo tryGetActivity(ComponentName componentName) {
        try {
            return getPackageManager().getActivityInfo(componentName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }

    @Override
    public void onInterrupt() {}
}

AndroidManifest.xml

Merge this into your manifest:

<application>
    <service
        android:label="@string/accessibility_service_name"
        android:name=".WindowChangeDetectingService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService"/>
        </intent-filter>
        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/accessibilityservice"/>
    </service>
</application>

Service Info

Put this in res/xml/accessibilityservice.xml:

<?xml version="1.0" encoding="utf-8"?>
<!-- These options MUST be specified here in order for the events to be received on first
 start in Android 4.1.1 -->
<accessibility-service
    xmlns:tools="http://schemas.android.com/tools"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews"
    android:description="@string/accessibility_service_description"
    xmlns:android="http://schemas.android.com/apk/res/android"
    tools:ignore="UnusedAttribute"/>

Enabling the Service

Each user of the app will need to explicitly enable the AccessibilityService in order for it to be used. See this StackOverflow answer for how to do this.

Note that the user won't be able to press the OK button when trying to enable the accessibility service if an app has placed an overlay on the screen, such as Velis Auto Brightness or Lux.

Alienation answered 24/12, 2014 at 22:5 Comment(27)
why not emphasize AccessibilityService need be turned on in settings?#11088258Randallrandan
@SalutonMondo, I guess I'd figured that people reading this would read the "Disadvantages" section since disadvantages are pretty important. But I think you're right; I can imagine people skimming over the details. I've added some instructions at the bottom to try to make this requirement more clear.Alienation
Precisely what means "Ensure you create your.app.ServiceSettingsActivity so you can provide a UI to users when they view the accessibility service in the Android settings." ?Franctireur
@lovemint, what I meant is: I specified an example settingsActivity of your.app.ServiceSettingsActivity, so you should change that to your own settings activity for your accessibility service. I think the settings activity is optional anyway, so I've removed that part from my answer to make it simpler.Alienation
Thanks very much, only last question. If i need to deal from API 8 to the current i should do that work in the code instead using XML?Franctireur
@lovemint, that's right. I just updated the example code to do this.Alienation
@Alienation I can confirm that this Service works perfectly. One strange behavior, I used an intent in the main activity to enable Accessibility Service. After this first activation the service it's activated but onAccessibilityEvent does not receive any event, but if i disable, and enable again Accessibility Service the service it is re-activated and onAccessibilityEvent begins to work.Franctireur
@lovemint, thanks for spotting that! Looks like a bug in Android 4.1.1. I just fixed it by specifying the accessibility service configuration in both the XML file and in the code. See the updated answer for an example.Alienation
@Alienation can you take a look on my question? #32158104 thanks very much!Franctireur
Thank you for your code. I created this app for you :)Decalescence
Hey, can it be useful for also getting the top activity's views, just like on "monitor" tool of the SDK ? I asked about it here: https://mcmap.net/q/89541/-is-it-possible-to-use-adb-commands-to-click-on-a-view-by-finding-its-id/878126Transistor
@androiddeveloper, I can't remember; it's been a long time since I played around with this. I'd suggest adjusting the AccessibilityService's settings to give it access to as much as possible, and then experiment with it to see if it provides what you're looking for.Alienation
@Alienation I've found this sample : github.com/seguri/GetForegroundActivity . Seems to work well. I'd like to ask you if you know how to get the views info of the activity on top.Transistor
"I don't know of any way to programmatically restart it correctly if you've stopped it." - actually you can disable and enable it as you wish, like so: https://mcmap.net/q/89542/-enable-and-disable-a-broadcast-receiverTransistor
@androiddeveloper: 1. That sample is already referenced 3 comments above your comment, and the sample is based on the code in this answer. :)Alienation
@androiddeveloper: 2. Regarding getting the top activity's view info, see my last reply to you.Alienation
@androiddeveloper: 3. Regarding programmatically starting an AccessibilityService, I was referring to actually starting it, as in startService, rather than enabling it. As far as I remember, programmatically re-enabling a programmatically-stopped AccessibilityService didn't start the service when I last tested this.Alienation
@Alienation Using disable&enable works fine. About the view info, if you find anything, please write about it on the post I written about.Transistor
@androiddeveloper, I just spent some time trying several techniques to programatically start and stop the service, and I couldn't get your disable & enable technique to work. Would you mind sharing how you did it? I've posted a question about this here: #40433949Alienation
@Alienation Currently, it is just checking when state of window is changed. My problem is to check which activity or app is open currently and I have my own keyboard in it. So every time I open my keyboard in other's app, it stats that it's my app rather than other app. Any solution on this?Beforetime
@JimitPatel, it's actually checking both the window state and whether the current window is an activity. Are you using the isActivity check from my code example? I just tested it and I'm not getting the problem you described.Alienation
@Alienation cool it's working... I messed up on some other part of the code. Thanks :)Beforetime
Say, I've found this app: play.google.com/store/apps/… , and it says it's recommended to use usage-stats permission instead of accessibility. How could it be better? I can't find any listener for it. Only polling of the current state.Transistor
@androiddeveloper, see the "Disadvantages" section in my answer. The accessibility service generally works well, but sometimes the events arrive out-of-order, among other problems. Polling is basically 100% reliable. I've switched to usage-stats in my app in Production and haven't had any issues since the switch.Alienation
@Alienation How come they are out of order? Can you reproduce it? Seems like a bugTransistor
@androiddeveloper, it's one of many bugs in Android :) As far as I remember it's timing-related so couldn't be reliably reproduced. Took months to catch it in action.Alienation
@Alienation Maybe you should report about it, then: issuetracker.google.com/issuesTransistor
T
88

Is there a native android way to get a reference to the currently running Activity from a service?

You may not own the "currently running Activity".

I have a service running on the background, and I would like to update my current Activity when an event occurs (in the service). Is there a easy way to do that (like the one I suggested above)?

  1. Send a broadcast Intent to the activity -- here is a sample project demonstrating this pattern
  2. Have the activity supply a PendingIntent (e.g., via createPendingResult()) that the service invokes
  3. Have the activity register a callback or listener object with the service via bindService(), and have the service call an event method on that callback/listener object
  4. Send an ordered broadcast Intent to the activity, with a low-priority BroadcastReceiver as backup (to raise a Notification if the activity is not on-screen) -- here is a blog post with more on this pattern
Tatianna answered 6/10, 2010 at 15:6 Comment(5)
Thanks, but as I do have a looot of activities, and don't want to update them all, I was looking for an android way to get the foregroundActivity. As you say, I should not be able to do that. Means I have to get my way around this.Kiyohara
@George: What you want wouldn't help you, anyway. As you point out, you have "a looot of activities". Hence, those are all separate classes. Hence, there is nothing your service can do with them without either a massive if-block of instanceof checks or refactoring the activities to share a common superclass or interface. And if you're going to refactor the activities, you may as well do it in a way that fits better with the framework and covers more scenarios, such as none of your activities being active. #4 is probably the least work and most flexible.Tatianna
Thanks, but I have a better solution. All my activities extends a customised BaseActivity class. I have set a ContextRegister that registeres the activity as current whenever it is on the foreground, with literraly 3 lines in my BaseActivity class. Still, thanks for the support.Kiyohara
@George: Watch out for memory leaks. Mutable static data members are to be avoided in Java wherever possible.Tatianna
I have tightly encapsualted the object, though I will indeed keep this in mind. Thanks.Kiyohara
C
27

It can be done by:

  1. Implement your own application class, register for ActivityLifecycleCallbacks - this way you can see what is going on with our app. On every on resume the callback assigns the current visible activity on the screen and on pause it removes the assignment. It uses method registerActivityLifecycleCallbacks() which was added in API 14.

    public class App extends Application {
    
    private Activity activeActivity;
    
    @Override
    public void onCreate() {
        super.onCreate();
        setupActivityListener();
    }
    
    private void setupActivityListener() {
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }
            @Override
            public void onActivityStarted(Activity activity) {
            }
            @Override
            public void onActivityResumed(Activity activity) {
                activeActivity = activity;
            }
            @Override
            public void onActivityPaused(Activity activity) {
                activeActivity = null;
            }
            @Override
            public void onActivityStopped(Activity activity) {
            }
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
            @Override
            public void onActivityDestroyed(Activity activity) {
            }
        });
    }
    
    public Activity getActiveActivity(){
        return activeActivity;
    }
    
    }
    
  2. In your service call getApplication() and cast it to your app class name (App in this case). Than you can call app.getActiveActivity() - that will give you a current visible Activity (or null when no activity is visible). You can get the name of the Activity by calling activeActivity.getClass().getSimpleName()

Catheycathi answered 27/11, 2016 at 20:38 Comment(6)
I am getting Nullpointer exeception at activeActivity.getClass().getSimpleName().can you please helpSigmatism
Well as you can see from the ActivityLifecycleCallbacks - in case the activity is not visible, application.getActiveActivity() returns null. That means that no activity is visible. You need to check for this in your service or wherever else you use this.Catheycathi
ok..but in case the activity resumes then this should not return null ?? My activity had launched and in its on resume I got soSigmatism
Hard to guess, try to put breakpoints at onActivityResumed(), onActivityPaused() and to getActiveActivity() to see how is it callet, if ever so.Catheycathi
@Sigmatism one must check if activity and activeActivity are same before assigning activeActivity with null in order to avoid errors due to intermingled calling order of lifecycle methods of various activities.Berenice
This approach works fine BUT if you have to activities in the back stack, then you changed the device orientation, getActiveActivity() will always return the activity in the background, not the visible one. Does anyone know how to solve this issue?Paraphernalia
O
18

I could not find a solution that our team would be happy with so we rolled our own. We use ActivityLifecycleCallbacks to keep track of current activity and then expose it through a service:

public interface ContextProvider {
    Context getActivityContext();
}

public class MyApplication extends Application implements ContextProvider {
    private Activity currentActivity;

    @Override
    public Context getActivityContext() {
         return currentActivity;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityStarted(Activity activity) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityResumed(Activity activity) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityPaused(Activity activity) {
                MyApplication.this.currentActivity = null;
            }

            @Override
            public void onActivityStopped(Activity activity) {
                // don't clear current activity because activity may get stopped after
                // the new activity is resumed
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                // don't clear current activity because activity may get destroyed after
                // the new activity is resumed
            }
        });
    }
}

Then configure your DI container to return instance of MyApplication for ContextProvider, e.g.

public class ApplicationModule extends AbstractModule {    
    @Provides
    ContextProvider provideMainActivity() {
        return MyApplication.getCurrent();
    }
}

(Note that implementation of getCurrent() is omitted from the code above. It's just a static variable that's set from the application constructor)

Oozy answered 29/7, 2016 at 4:36 Comment(1)
one must check if activity and currentActivity are same before assigning currentActivity with null in order to avoid errors due to intermingled calling order of lifecycle methods of various activities.Berenice
A
15

Use ActivityManager

If you only want to know the application containing the current activity, you can do so using ActivityManager. The technique you can use depends on the version of Android:

Benefits

  • Should work in all Android versions to-date.

Disadvantages

  • Doesn't work in Android 5.1+ (it only returns your own app)
  • The documentation for these APIs says they're only intended for debugging and management user interfaces.
  • If you want real-time updates, you need to use polling.
  • Relies on a hidden API: ActivityManager.RunningAppProcessInfo.processState
  • This implementation doesn't pick up the app switcher activity.

Example (based on KNaito's code)

public class CurrentApplicationPackageRetriever {

    private final Context context;

    public CurrentApplicationPackageRetriever(Context context) {
        this.context = context;
    }

    public String get() {
        if (Build.VERSION.SDK_INT < 21)
            return getPreLollipop();
        else
            return getLollipop();
    }

    private String getPreLollipop() {
        @SuppressWarnings("deprecation")
        List<ActivityManager.RunningTaskInfo> tasks =
            activityManager().getRunningTasks(1);
        ActivityManager.RunningTaskInfo currentTask = tasks.get(0);
        ComponentName currentActivity = currentTask.topActivity;
        return currentActivity.getPackageName();
    }

    private String getLollipop() {
        final int PROCESS_STATE_TOP = 2;

        try {
            Field processStateField = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");

            List<ActivityManager.RunningAppProcessInfo> processes =
                activityManager().getRunningAppProcesses();
            for (ActivityManager.RunningAppProcessInfo process : processes) {
                if (
                    // Filters out most non-activity processes
                    process.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                    &&
                    // Filters out processes that are just being
                    // _used_ by the process with the activity
                    process.importanceReasonCode == 0
                ) {
                    int state = processStateField.getInt(process);

                    if (state == PROCESS_STATE_TOP) {
                        String[] processNameParts = process.processName.split(":");
                        String packageName = processNameParts[0];

                        /*
                         If multiple candidate processes can get here,
                         it's most likely that apps are being switched.
                         The first one provided by the OS seems to be
                         the one being switched to, so we stop here.
                         */
                        return packageName;
                    }
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        return null;
    }

    private ActivityManager activityManager() {
        return (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    }

}

Manifest

Add the GET_TASKS permission to AndroidManifest.xml:

<!--suppress DeprecatedClassUsageInspection -->
<uses-permission android:name="android.permission.GET_TASKS" />
Alienation answered 20/4, 2015 at 12:43 Comment(2)
this doesn't work on Android M anymore. getRunningAppProcesses() now returns only your application's packagePalanquin
Doesn't work for API Level 22 (Android 5.1) as well. Tested on Build LPB23Fondue
G
10

I'm using this for my tests. It's API > 19, and only for activities of your app, though.

@TargetApi(Build.VERSION_CODES.KITKAT)
public static Activity getRunningActivity() {
    try {
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Object activityThread = activityThreadClass.getMethod("currentActivityThread")
                .invoke(null);
        Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
        activitiesField.setAccessible(true);
        ArrayMap activities = (ArrayMap) activitiesField.get(activityThread);
        for (Object activityRecord : activities.values()) {
            Class activityRecordClass = activityRecord.getClass();
            Field pausedField = activityRecordClass.getDeclaredField("paused");
            pausedField.setAccessible(true);
            if (!pausedField.getBoolean(activityRecord)) {
                Field activityField = activityRecordClass.getDeclaredField("activity");
                activityField.setAccessible(true);
                return (Activity) activityField.get(activityRecord);
            }
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    throw new RuntimeException("Didn't find the running activity");
}
Gloxinia answered 4/10, 2015 at 1:10 Comment(1)
Replace ArrayMap by Map and it'll work on 4.3 too. Not tested earlier Android versions.Inapposite
U
4

Here's what I suggest and what has worked for me. In your application class, implement an Application.ActivityLifeCycleCallbacks listener and set a variable in your application class. Then query the variable as needed.

class YourApplication: Application.ActivityLifeCycleCallbacks {
    
    var currentActivity: Activity? = null

    fun onCreate() {
        registerActivityLifecycleCallbacks(this)
    }

    ...
    
    override fun onActivityResumed(activity: Activity) {
        currentActivity = activity
    }
}
Uzziel answered 11/8, 2020 at 22:41 Comment(0)
S
2

Use this code for API 21 or above. This works and gives better result compared to the other answers, it detects perfectly the foreground process.

if (Build.VERSION.SDK_INT >= 21) {
    String currentApp = null;
    UsageStatsManager usm = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE);
    long time = System.currentTimeMillis();
    List<UsageStats> applist = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time);
    if (applist != null && applist.size() > 0) {
        SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
        for (UsageStats usageStats : applist) {
            mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);

        }
        if (mySortedMap != null && !mySortedMap.isEmpty()) {
            currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
        }
    }
Sabatier answered 30/7, 2016 at 12:25 Comment(3)
Can you please elaborate on how the results from this method are better than the other answers?Alienation
Does this work with Notification Menu as well. I aam facing an issue in which if I get a call or message. UsageStatsManager starts giving me package name of systemUi instead of my app.Sudarium
@kukroid, post a separate question and include your source code, device information, and which messaging & phone apps you're using.Alienation
C
2

I like the idea of the Application.ActivityLifecycleCallbacks. But it can be a bit tricky getting the top activity

  • Your app might have multiple activities
  • Some activities might get destroyed
  • Some activities might go to background

To handle all those cases, you need to track each activity life cycle. This is exactly what I did with my below solution.

It all consolidates into a single call of getTopForegroundActivity() that returns the top foreground activity or null if no activities in the stack or non of them are in the foreground.

Usage

public class MyApp extends Application {

    private ActivityTracker activityTracker;

    @Override
    public void onCreate() {
        super.onCreate();

        activityTracker = new ActivityTracker();
        registerActivityLifecycleCallbacks(activityTracker);
        
        ...

        Activity activity = activityTracker.getTopForegroundActivity();
        if(activity != null) {
            // Do something
        }
    }
}

Source

public class ActivityTracker implements Application.ActivityLifecycleCallbacks {
    private final Map<Activity, ActivityData> activities = new HashMap<>();


    public Activity getTopForegroundActivity() {
        if (activities.isEmpty()) {
            return null;
        }
        ArrayList<ActivityData> list = new ArrayList<>(activities.values());
        Collections.sort(list, (o1, o2) -> {
            int compare = Long.compare(o2.started, o1.started);
            return compare != 0 ? compare : Long.compare(o2.resumed, o1.resumed);
        });

        ActivityData topActivity = list.get(0);
        return topActivity.started != -1 ? topActivity.activity : null;
    }


    @Override
    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
        activities.put(activity, new ActivityData(activity));
    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.started = System.currentTimeMillis();
        }
    }

    @Override
    public void onActivityResumed(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.resumed = System.currentTimeMillis();
        }
    }

    @Override
    public void onActivityPaused(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.resumed = -1;
        }
    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.started = -1;
        }
    }

    @Override
    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

    }

    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {
        activities.remove(activity);
    }

    private static class ActivityData {

        public final Activity activity;
        public long started;
        public long resumed;

        private ActivityData(Activity activity) {
            this.activity = activity;
        }
    }
}
Congruence answered 21/3, 2021 at 18:2 Comment(0)
A
1

I don't know if it's a stupid answer, but resolved this problem by storing a flag in shared preferences every time I entered onCreate() of any activity, then I used the value from shered preferences to find out what it's the foreground activity.

Adiell answered 8/3, 2017 at 3:13 Comment(1)
We are looking for a native way to do itRaynell
P
-1

Here is my answer that works just fine...

You should be able to get current Activity in this way... If you structure your app with a few Activities with many fragments and you want to keep track of what is your current Activity, it would take a lot of work though. My senario was I do have one Activity with multiple Fragments. So I can keep track of Current Activity through Application Object, which can store all of the current state of Global variables.

Here is a way. When you start your Activity, you store that Activity by Application.setCurrentActivity(getIntent()); This Application will store it. On your service class, you can simply do like Intent currentIntent = Application.getCurrentActivity(); getApplication().startActivity(currentIntent);

Phenosafranine answered 26/3, 2015 at 18:31 Comment(1)
This assumes that the service runs in the same process as the Activity, right?Araujo
B
-3

Just recently found out about this. With apis as:

  • minSdkVersion 19
  • targetSdkVersion 26

    ActivityManager.getCurrentActivity(context)

Hope this is of any use.

Baluchistan answered 14/3, 2018 at 20:52 Comment(2)
Had my hopes up. Perhaps when this was answered it existed, but as of now there is no getCurrentActivity() function in the ActivityManager class (developer.android.com/reference/android/app/ActivityManager). Funny that this function does exist: ActivityManager.isUserAMonkey().Carlin
This detects if you're using a monkey testing agent to test your activity. Though a funny name, it might be quite useful for testing (and making other people laugh, obvs.) developer.android.com/studio/test/monkeyrunnerCarney

© 2022 - 2024 — McMap. All rights reserved.