Android - How to detect when 3d party app is starting
Asked Answered
S

1

15

I'm currently developing an app for tablets which will show commercials in stores. I need to add a password protection for the settings application. To achieve this I need to detect that the settings app is started in a background service. For devices with Android below API 21 this can be done with getRunningTasks(). Unfortunately this method is deprecated now. I tried achieving my goal with this answer, but things aren't working as I need. I'm trying to listen for V/WindowManager( 740): Adding window Window{1f4535ef u0 com.android.settings/com.android.settings.Settings} at 9 of 16 (before Window{e14eed2 u0 Starting com.android.settings}) but I can't see it as an output. I'm using the code from the answer I referenced above. Instead of Toasting it I'm printing it in the logcat.

Can you tell me how my goal can be achieved for android versions above API 21? I know it is possible because apps like Smart AppLock does it. Thanks in advance!

Shirlshirlee answered 12/9, 2015 at 8:49 Comment(0)
W
7

It currently is not possible to get the foreground app on Android 5.1.1+ unless you are granted the system level permission android.permission.PACKAGE_USAGE_STATS. Note that several major vendors have removed the system activity that grants access to the UsageStats API from their devices. This means that apps on those devices can never acquire the necessary permission. This may change with a system update.

You mentioned that Smart Lock (App Protector) can detect the current foreground activity on Android M. The first time you open Smart Lock (or similar apps) you are prompted to grant the PACKAGE_USAGE_STATS permission. So we can conclude that they are relying on UsageStatsManager to get the foreground application.

I have tried finding a workaround (hack) to get the foreground app on Android M without a system level permission. Please read and try out my script in my answer here: Android 5.1.1 and above - getRunningAppProcesses() returns my application package only


Logcat

Unless you have root permission, reading the logcat to get the foreground application will not work. Reading the logcat since Jelly Bean only returns log messages from your application only.


Useful links:

How to check if "android.permission.PACKAGE_USAGE_STATS" permission is given?

How to use UsageStatsManager?

https://code.google.com/p/android-developer-preview/issues/detail?id=2347


Edit:

After digging through Android source I wrote the following code to get the foreground app on Android M without any permissions. I have only tested it on a Nexus 9 running the latest developer preview. Please copy and paste the code into a class in your project and test it out by calling the static method getForegroundApp():

/** first app user */
public static final int AID_APP = 10000;

/** offset for uid ranges for each user */
public static final int AID_USER = 100000;

public static String getForegroundApp() {
  File[] files = new File("/proc").listFiles();
  int lowestOomScore = Integer.MAX_VALUE;
  String foregroundProcess = null;

  for (File file : files) {
    if (!file.isDirectory()) {
      continue;
    }

    int pid;
    try {
      pid = Integer.parseInt(file.getName());
    } catch (NumberFormatException e) {
      continue;
    }

    try {
      String cgroup = read(String.format("/proc/%d/cgroup", pid));

      String[] lines = cgroup.split("\n");

      if (lines.length != 2) {
        continue;
      }

      String cpuSubsystem = lines[0];
      String cpuaccctSubsystem = lines[1];

      if (!cpuaccctSubsystem.endsWith(Integer.toString(pid))) {
        // not an application process
        continue;
      }

      if (cpuSubsystem.endsWith("bg_non_interactive")) {
        // background policy
        continue;
      }

      String cmdline = read(String.format("/proc/%d/cmdline", pid));

      if (cmdline.contains("com.android.systemui")) {
        continue;
      }

      int uid = Integer.parseInt(
          cpuaccctSubsystem.split(":")[2].split("/")[1].replace("uid_", ""));
      if (uid >= 1000 && uid <= 1038) {
        // system process
        continue;
      }

      int appId = uid - AID_APP;
      int userId = 0;
      // loop until we get the correct user id.
      // 100000 is the offset for each user.
      while (appId > AID_USER) {
        appId -= AID_USER;
        userId++;
      }

      if (appId < 0) {
        continue;
      }

      // u{user_id}_a{app_id} is used on API 17+ for multiple user account support.
      // String uidName = String.format("u%d_a%d", userId, appId);

      File oomScoreAdj = new File(String.format("/proc/%d/oom_score_adj", pid));
      if (oomScoreAdj.canRead()) {
        int oomAdj = Integer.parseInt(read(oomScoreAdj.getAbsolutePath()));
        if (oomAdj != 0) {
          continue;
        }
      }

      int oomscore = Integer.parseInt(read(String.format("/proc/%d/oom_score", pid)));
      if (oomscore < lowestOomScore) {
        lowestOomScore = oomscore;
        foregroundProcess = cmdline;
      }

    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  return foregroundProcess;
}

private static String read(String path) throws IOException {
  StringBuilder output = new StringBuilder();
  BufferedReader reader = new BufferedReader(new FileReader(path));
  output.append(reader.readLine());
  for (String line = reader.readLine(); line != null; line = reader.readLine()) {
    output.append('\n').append(line);
  }
  reader.close();
  return output.toString();
}
Weirick answered 12/9, 2015 at 9:6 Comment(7)
Thank you for your detailed answer. I appreciate your work. The code you provided works on Android 5.1.1 but unfortunately not for every app. getGoregroundApp() returns null for the Settings application. Tested on Nexus 5.Shirlshirlee
@Crash-ID interesting. If you get the pid of com.android.settings and check if the file (/proc/[pid]) exists it will say it doesn't even though it does. Looking into it...Weirick
@ Crash-ID - Is it possible for you to comment on what kind of apps the proposed solution by Jared Rummler doesn't work on ? @ Jared/@Crash-ID. As far as I can tell, it seems to work pretty well. Is it guaranteed to work on all user installed apps ?Draper
@Draper it won't work for system apps when selinux is enforcing. You should use UsageStatsManager instead.Weirick
@ Jared. Thank you for responding, and for the brilliant answer. After running more experiments since previous post (Mar 20), foreground app detection based on this approach is working on Samsung Galaxy S4, Samsung Galaxy Note, Asus Zenfone, and not working on HTC one 8, Sony Experia (sorry for also posting on other related post). Also, only user installed apps are of interest to us - so not detecting system apps is ok, in our use case.Draper
@JaredRummler does this code only work on Android M? I'm testing it on a Samsung Tab Pro 8.4 with Android 4.4.2, and I'm getting null foregroundProcess. After debugging, it seems that the if statement that determines if it's an application process always returns true, hence causing it to continue the loop. It seems that on this device, cpuaccctSubsystem never ends with pid.Kulun
Does not work on one plus one android version 5.1.1 cgroup lines = 3 in all applications in the loop.Clavicytherium

© 2022 - 2024 — McMap. All rights reserved.