/*
* Copyright (C) 2015. Jared Rummler <jared.rummler@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.jaredrummler.android.processes.models;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Parcel;
import com.jaredrummler.android.processes.AndroidProcesses;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
public class AndroidAppProcess extends AndroidProcess {
private static final boolean SYS_SUPPORTS_SCHEDGROUPS = new File("/dev/cpuctl/tasks").exists();
// The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers, and underscores ('_').
// However, individual package name parts may only start with letters.
// A process name can contain a colon.
private static final Pattern PROCESS_NAME_PATTERN = Pattern.compile(
"^([A-Za-z]{1}[A-Za-z0-9_]*[\\.|:])*[A-Za-z][A-Za-z0-9_]*$");
/** {@code true} if the process is in the foreground */
public final boolean foreground;
/** The user id of this process. */
public final int uid;
public AndroidAppProcess(int pid) throws IOException, NotAndroidAppProcessException {
super(pid);
if (name == null || !PROCESS_NAME_PATTERN.matcher(name).matches() ||
!new File("/data/data", getPackageName()).exists()) {
throw new NotAndroidAppProcessException(pid);
}
final boolean foreground;
int uid;
if (SYS_SUPPORTS_SCHEDGROUPS) {
Cgroup cgroup = cgroup();
ControlGroup cpuacct = cgroup.getGroup("cpuacct");
ControlGroup cpu = cgroup.getGroup("cpu");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (cpu == null || cpuacct == null || !cpuacct.group.contains("pid_")) {
throw new NotAndroidAppProcessException(pid);
}
foreground = !cpu.group.contains("bg_non_interactive");
try {
uid = Integer.parseInt(cpuacct.group.split("/")[1].replace("uid_", ""));
} catch (Exception e) {
uid = status().getUid();
}
AndroidProcesses.log("name=%s, pid=%d, uid=%d, foreground=%b, cpuacct=%s, cpu=%s",
name, pid, uid, foreground, cpuacct.toString(), cpu.toString());
} else {
if (cpu == null || cpuacct == null || !cpu.group.contains("apps")) {
throw new NotAndroidAppProcessException(pid);
}
foreground = !cpu.group.contains("bg_non_interactive");
try {
uid = Integer.parseInt(cpuacct.group.substring(cpuacct.group.lastIndexOf("/") + 1));
} catch (Exception e) {
uid = status().getUid();
}
AndroidProcesses.log("name=%s, pid=%d, uid=%d foreground=%b, cpuacct=%s, cpu=%s",
name, pid, uid, foreground, cpuacct.toString(), cpu.toString());
}
} else {
Stat stat = stat();
Status status = status();
// https://github.com/android/platform_system_core/blob/jb-mr1-release/libcutils/sched_policy.c#L245-256
foreground = stat.policy() == 0; // SCHED_NORMAL
uid = status.getUid();
AndroidProcesses.log("name=%s, pid=%d, uid=%d foreground=%b", name, pid, uid, foreground);
}
this.foreground = foreground;
this.uid = uid;
}
/**
* @return the app's package name
* @see #name
*/
public String getPackageName() {
return name.split(":")[0];
}
/**
* Retrieve overall information about the application package.
*
* <p>Throws {@link PackageManager.NameNotFoundException} if a package with the given name can
* not be found on the system.</p>
*
* @param context
* the application context
* @param flags
* Additional option flags. Use any combination of
* {@link PackageManager#GET_ACTIVITIES}, {@link PackageManager#GET_GIDS},
* {@link PackageManager#GET_CONFIGURATIONS}, {@link PackageManager#GET_INSTRUMENTATION},
* {@link PackageManager#GET_PERMISSIONS}, {@link PackageManager#GET_PROVIDERS},
* {@link PackageManager#GET_RECEIVERS}, {@link PackageManager#GET_SERVICES},
* {@link PackageManager#GET_SIGNATURES}, {@link PackageManager#GET_UNINSTALLED_PACKAGES}
* to modify the data returned.
* @return a PackageInfo object containing information about the package.
*/
public PackageInfo getPackageInfo(Context context, int flags)
throws PackageManager.NameNotFoundException {
return context.getPackageManager().getPackageInfo(getPackageName(), flags);
}
@Override public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeByte((byte) (foreground ? 0x01 : 0x00));
dest.writeInt(uid);
}
protected AndroidAppProcess(Parcel in) {
super(in);
foreground = in.readByte() != 0x00;
uid = in.readInt();
}
public static final Creator<AndroidAppProcess> CREATOR = new Creator<AndroidAppProcess>() {
@Override public AndroidAppProcess createFromParcel(Parcel source) {
return new AndroidAppProcess(source);
}
@Override public AndroidAppProcess[] newArray(int size) {
return new AndroidAppProcess[size];
}
};
public static final class NotAndroidAppProcessException extends Exception {
public NotAndroidAppProcessException(int pid) {
super(String.format("The process %d does not belong to any application", pid));
}
}
}