52

In Android L, Google has disabled getRunningTasks. Now it can only return own apps task and the home launcher. I can no longer get other apps tasks. Our app needs that method to determine current top app. Any one has another method to do this?

I have searched in Google, no more topics about this except this: https://code.google.com/p/android-developer-preview/issues/detail?id=29

Ligen Yao
  • 641
  • 1
  • 6
  • 6

5 Answers5

45

For a recent project that I worked on, I also need to detect when certain applications are launched. All my research lead to the getRunningTasks method, which is deprecated starting from Lollipop. However, to my surprises, I discovered that some of the app lock apps still work on lollipop devices, so they must have come up with a solution to get around this. So I dug a little deeper. Here is what I found out:

    1. On pre-L devices, they still use getRunningTasks
    1. On L devices, they use getRunningAppProcesses, which returns a list of processes currently running on the devices. You might think "well, that is not useful". Each processInfo has a attributed called importance. When an app becomes top activity, its processInfo importance also changes to IMPORTANCE_FOREGROUND. So you can filter out those processes that are not in foreground. From a each ProcessInfo, you can also ask a list of packages it loaded. You can then check if the list contains the same package that the app when are trying "protected".

Some sample code to detect when the default calendar app is launched:

public class DetectCalendarLaunchRunnable implements Runnable {

@Override
public void run() {
  String[] activePackages;
  if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) {
    activePackages = getActivePackages();
  } else {
    activePackages = getActivePackagesCompat();
  }
  if (activePackages != null) {
    for (String activePackage : activePackages) {
      if (activePackage.equals("com.google.android.calendar")) {
        //Calendar app is launched, do something
      }
    }
  }
  mHandler.postDelayed(this, 1000);
}

String[] getActivePackagesCompat() {
  final List<ActivityManager.RunningTaskInfo> taskInfo = mActivityManager.getRunningTasks(1);
  final ComponentName componentName = taskInfo.get(0).topActivity;
  final String[] activePackages = new String[1];
  activePackages[0] = componentName.getPackageName();
  return activePackages;
}

String[] getActivePackages() {
  final Set<String> activePackages = new HashSet<String>();
  final List<ActivityManager.RunningAppProcessInfo> processInfos = mActivityManager.getRunningAppProcesses();
  for (ActivityManager.RunningAppProcessInfo processInfo : processInfos) {
    if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
      activePackages.addAll(Arrays.asList(processInfo.pkgList));
    }
  }
  return activePackages.toArray(new String[activePackages.size()]);
}
}

Note: getRunningAppProcesses is also intended for debugging or "building a user-facing process management UI". Not sure if google will close this backdoor the similar way they did to getRunningTasks.

So no, you can't get the topActivity anymore. But with a little bit hack you can achieve similar result.

sunxin8086
  • 565
  • 5
  • 11
  • 2
    I don't see how this gives you the top activity. It returns multiple processes with IMPORTANCE_FOREGROUND, so there is no way to know which one is on top. Additionally, it returns multiple packages in each process, any one of which could be on top. So, am I missing something something? – user496854 Nov 30 '14 at 18:44
  • 1
    It doesn't get the top activity, but the example that I gave above, you can detect when certain app is launched, which is good enough for a lot of the use cases. – sunxin8086 Dec 01 '14 at 21:17
  • 1
    Helps a lot. Combined yours and KNaito answer together and working fine. Works perfectly. – Smeet Mar 05 '15 at 13:01
  • I found that your code also returns multiple packages: both the process containing the activity and processes that are *used* by this process. One example is the Firefox app, which seems to use Google GMS and YouTube processes. To fix this, I added the following check to the `if` statement: `process.importanceReasonCode == 0`. – Sam Apr 14 '15 at 09:08
  • the getRunningTasks is returning me a security exception. Why? – inquisitive May 21 '15 at 06:27
  • 9
    @sunxin8086 unfortunately, this doesn't work anymore on Android M. am.getRunningAppProcesses() returns your app package only. – Lior Iluz Jun 03 '15 at 11:33
  • As to what @LiorIluz has said - this is the answer Google has about this issue - https://code.google.com/p/android-developer-preview/issues/detail?id=2347. In short, you now need real_get_tasks, which only system apps can get. – Ginandi Jul 31 '15 at 13:32
34

As MKY mentioned, getRunningTasks() method does not work for getting the current application in Lollipop.

As sunxin8086 wrote, the one way for getting the running applications is by using getRunningAppsProcesses() method. However, the condition info.importance == IMPORTANCE_FOREGROUND can not determine the current app uniquely.

The better approach to determine the current foreground application may be checking the processState field in RunningAppProcessInfo object. This field is a hidden field, but you can see it in the RunningAppProcessInfo class. If this value is ActivityManager.PROCESS_STATE_TOP (which is also hidden static constant), the process is the current foreground process.

For example the code is

final int PROCESS_STATE_TOP = 2;
ActivityManager.RunningAppProcessInfo currentInfo = null;
Field field = null;
try {
    field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");
} catch (Exception ignored) {
}
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appList = am.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo app : appList) {
    if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 
            && app.importanceReasonCode == ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN) {
        Integer state = null;
        try {
            state = field.getInt(app);
        } catch (Exception e) {
        }
        if (state != null && state == PROCESS_STATE_TOP) {
            currentInfo = app;
            break;
        }
    }
}
return currentInfo;

Note: processState field does not exist in pre-Lolipop. Please check that Build.VERSION.SDK_INT >= 21 before running the above code. The above code works only for Lollipop+.

The other approach, by Gaston (which is quite different), and the meaning of 'current application' is slightly different from this approach.

Please choose one for your purpose.


[EDIT]

As Sam pointed out, I modified START_TASK_TO_FRONT by PROCESS_STATE_TOP. (Both values are 2)

[EDIT2]

Sam has a new find! To determine the foreground application uniquely, one more condition

process.importanceReasonCode == 0

is necessary. The above code has been updated. Thanks!

Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
KNaito
  • 3,930
  • 2
  • 19
  • 21
  • 1
    I tested it on Android Studio Nexus 5 Emulator, seems to work, still need actual testing on a physical device. – gordon1hd1 Feb 03 '15 at 09:34
  • 1
    Running very well on a Nexus 6. – 3c71 Feb 07 '15 at 17:47
  • Thanks for the report! I also check the Nexus 7 and it looks working. – KNaito Feb 08 '15 at 16:45
  • 1
    its working fine in samsung S5 and in LG with OS lollipop @KNaito – Erum Feb 09 '15 at 11:40
  • 1
    Great! Save lots of time. Was facing same issue. Working like Charm. – Smeet Mar 05 '15 at 12:59
  • 1
    I think it makes more sense to compare `RunningAppProcessInfo.processState` to `RunningAppProcessInfo.PROCESS_STATE_TOP`. (Both constants seem to have the value `2`, but your code is a bit misleading to read.) – Sam Apr 13 '15 at 00:07
  • 3
    I found that your code also returns multiple packages: both the process containing the activity and processes that are *used* by this process. One example is the Firefox app, which seems to use Google GMS and YouTube processes. To fix this, I added the following check to the `if` statement: `process.importanceReasonCode == 0`. – Sam Apr 14 '15 at 09:08
  • Thanks for advice, Sam. I edit my answer again to add your condition. – KNaito Apr 14 '15 at 10:28
  • 4
    Great find. Thanks. It's important to point out that you should use the app.pkgList[0] to get the current running application's package and not app.processName. Most of the time they will be the same but sometimes not, like when running TuneIn Pro application. – Lior Iluz May 24 '15 at 00:59
  • Thanks Lior for valuable comment. – KNaito May 25 '15 at 08:43
  • 8
    @KNaito unfortunately, this doesn't work anymore on Android M. am.getRunningAppProcesses() returns your app package only. – Lior Iluz Jun 03 '15 at 11:19
  • Thanks Lior. We should be looking for new approach for Android M/6.0... I hope Google to prepare another way.... – KNaito Jun 04 '15 at 00:45
  • 1
    @KNaito I've started a new question with all the current options we have. Hopefully the community will find another way. http://stackoverflow.com/questions/30619349/android-m-getrunningappprocesses-returns-my-application-package-only – Lior Iluz Jun 04 '15 at 05:44
  • 1
    @KNaito RecentsActivity process has state 1 (PROCESS_STATE_PERSISTENT_UI). It is important when you monitor your app foreground state in Kiosk mode. – falko Jul 21 '15 at 21:27
  • 1
    I wonder if there is a way to get the "`processState`" from parsing files in /proc/[pid]. I can get the current running apps on Android M by parsing the output of `ps` in a shell. I can't find a way to get the current running app though. – Jared Rummler Aug 28 '15 at 09:59
  • 1
    @KNaito This is not working in 5.1.1 either in background. When app is running (in Foreground) then it working but when other applications are in foreground then it throws exception `Attempt to read from field 'java.lang.String android.app.ActivityManager$RunningAppProcessInfo.processName' on a null object reference` – maddy d Sep 02 '15 at 16:05
  • @Jared Rummier See Lior's page, http://stackoverflow.com/questions/30619349/android-5-1-1-and-above-getrunningappprocesses-returns-my-application-packag – KNaito Sep 03 '15 at 08:09
  • 1
    @KNaito, thanks. I actually put a bounty on it and I have an answer there. I literally just found a way to get the foreground app on M. Still testing to make sure. I'll update my answer probably tomorrow. – Jared Rummler Sep 03 '15 at 09:00
30

Here's an exact solution to get current top activity on your Android L/Lollipop devices and Android M/Marshmallow devices.

First call this line of code:(One time)

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

The above code will open a screen named "Apps with usage access". Just check the radio button to on/true to allow usage access. Now call the following method in your service or anywhere you want:

public void getTopActivtyFromLolipopOnwards() {
    String topPackageName;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        UsageStatsManager mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
        long time = System.currentTimeMillis();
        // We get usage stats for the last 10 seconds
        List < UsageStats > stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 10, time);
        // Sort the stats by the last time used
        if (stats != null) {
            SortedMap < Long, UsageStats > mySortedMap = new TreeMap < Long, UsageStats > ();
            for (UsageStats usageStats: stats) {
                mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
            }
            if (mySortedMap != null && !mySortedMap.isEmpty()) {
                topPackageName = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
                Log.e("TopPackage Name", topPackageName);
            }
        }
    }
}

add permission

<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
     tools:ignore="ProtectedPermissions" />

This will return the package name of currently running activity , whether it is facebook or whatsapp.

The only complication of this method is you need to prompt user for allowing app usage stats ... i.e. the first step.

Hope! this helps everyone.

Tristan
  • 3,530
  • 3
  • 30
  • 39
Udit Kapahi
  • 2,277
  • 1
  • 27
  • 25
  • 1
    Thanks for 'tools:ignore="ProtectedPermissions"'. – jiashie Dec 01 '15 at 09:26
  • For the ones searching to get the top activity's name, this returns the top activity's package name and not the top activity's name – ajan Feb 29 '16 at 08:25
  • PACKAGE_USAGE_STATS permission occures error, 'Permission is only granted to system apps' – hoi Mar 29 '16 at 03:41
  • 1
    Works Fine , But is there any way to filter notification, That means ,when a notification appears this code showing notifications also as Foreground process...i only want get Foreground Activity .. – Lins Louis Oct 20 '16 at 06:24
  • Works but is there currently a way to do the same thing without prompting user? – Alice Van Der Land Jan 26 '19 at 20:25
5
private String getProcess() throws Exception {
    if (Build.VERSION.SDK_INT >= 21) {
        return getProcessNew();
    } else {
        return getProcessOld();
    }
}

//API 21 and above
private String getProcessNew() throws Exception {
    String topPackageName = null;
    UsageStatsManager usage = (UsageStatsManager) context.getSystemService(Constant.USAGE_STATS_SERVICE);
    long time = System.currentTimeMillis();
    List<UsageStats> stats = usage.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - ONE_SECOND * 10, time);
    if (stats != null) {
        SortedMap<Long, UsageStats> runningTask = new TreeMap<Long,UsageStats>();
        for (UsageStats usageStats : stats) {
            runningTask.put(usageStats.getLastTimeUsed(), usageStats);
        }
        if (runningTask.isEmpty()) {
            return null;
        }
        topPackageName =  runningTask.get(runningTask.lastKey()).getPackageName();
    }
    return topPackageName;
}

//API below 21
@SuppressWarnings("deprecation")
private String getProcessOld() throws Exception {
    String topPackageName = null;
    ActivityManager activity = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> runningTask = activity.getRunningTasks(1);
    if (runningTask != null) {
        RunningTaskInfo taskTop = runningTask.get(0);
        ComponentName componentTop = taskTop.topActivity;
        topPackageName = componentTop.getPackageName();
    }
    return topPackageName;
}

//required permissions
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<uses-permission android:name="android.permission.GET_TASKS"/>
user4759293
  • 59
  • 1
  • 2
3

I Think its not possible to get other app's tasks,

This is what documentation says

With the introduction of the new concurrent documents and activities tasks feature in the upcoming release (see Concurrent documents and activities in Recents screen below), the ActivityManager.getRecentTasks() method is now deprecated to improve user privacy. For backward compatibility, this method still returns a small subset of its data, including the calling application’s own tasks and possibly some other non-sensitive tasks (such as Home). If your app is using this method to retrieve its own tasks, use android.app.ActivityManager.getAppTasks() instead to retrieve that information.

Check out the api overview of Android L here https://developer.android.com/preview/api-overview.html#Behaviors

Mohan Krishna
  • 352
  • 3
  • 15