package biz.bokhorst.xprivacy;
import java.lang.reflect.Field;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.location.Location;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
public class PrivacyManager {
public static final boolean cVersion3 = true;
// This should correspond with restrict_<name> in strings.xml
public static final String cAccounts = "accounts";
public static final String cBrowser = "browser";
public static final String cCalendar = "calendar";
public static final String cCalling = "calling";
public static final String cClipboard = "clipboard";
public static final String cContacts = "contacts";
public static final String cDictionary = "dictionary";
public static final String cEMail = "email";
public static final String cIdentification = "identification";
public static final String cInternet = "internet";
public static final String cIPC = "ipc";
public static final String cLocation = "location";
public static final String cMedia = "media";
public static final String cMessages = "messages";
public static final String cNetwork = "network";
public static final String cNfc = "nfc";
public static final String cNotifications = "notifications";
public static final String cOverlay = "overlay";
public static final String cPhone = "phone";
public static final String cSensors = "sensors";
public static final String cShell = "shell";
public static final String cStorage = "storage";
public static final String cSystem = "system";
public static final String cView = "view";
// This should correspond with the above definitions
private static final String cRestrictionNames[] = new String[] { cAccounts, cBrowser, cCalendar, cCalling,
cClipboard, cContacts, cDictionary, cEMail, cIdentification, cInternet, cIPC, cLocation, cMedia, cMessages,
cNetwork, cNfc, cNotifications, cOverlay, cPhone, cSensors, cShell, cStorage, cSystem, cView };
public static List<String> cMethodNoState = Arrays.asList(new String[] { "IntentFirewall", "checkPermission",
"checkUidPermission" });
// Setting names
public final static String cSettingSerial = "Serial";
public final static String cSettingLatitude = "Latitude";
public final static String cSettingLongitude = "Longitude";
public final static String cSettingAltitude = "Altitude";
public final static String cSettingMac = "Mac";
public final static String cSettingIP = "IP";
public final static String cSettingImei = "IMEI";
public final static String cSettingPhone = "Phone";
public final static String cSettingId = "ID";
public final static String cSettingGsfId = "GSF_ID";
public final static String cSettingAdId = "AdId";
public final static String cSettingMcc = "MCC";
public final static String cSettingMnc = "MNC";
public final static String cSettingCountry = "Country";
public final static String cSettingOperator = "Operator";
public final static String cSettingIccId = "ICC_ID";
public final static String cSettingSubscriber = "Subscriber";
public final static String cSettingSSID = "SSID";
public final static String cSettingUa = "UA";
public final static String cSettingOpenTab = "OpenTab";
public final static String cSettingSelectedCategory = "SelectedCategory";
public final static String cSettingFUsed = "FUsed";
public final static String cSettingFInternet = "FInternet";
public final static String cSettingFRestriction = "FRestriction";
public final static String cSettingFRestrictionNot = "FRestrictionNot";
public final static String cSettingFPermission = "FPermission";
public final static String cSettingFOnDemand = "FOnDemand";
public final static String cSettingFOnDemandNot = "FOnDemandNot";
public final static String cSettingFUser = "FUser";
public final static String cSettingFSystem = "FSystem";
public final static String cSettingSortMode = "SortMode";
public final static String cSettingSortInverted = "SortInverted";
public final static String cSettingModifyTime = "ModifyTime";
public final static String cSettingTheme = "Theme";
public final static String cSettingSalt = "Salt";
public final static String cSettingVersion = "Version";
public final static String cSettingFirstRun = "FirstRun";
public final static String cSettingTutorialMain = "TutorialMain";
public final static String cSettingTutorialDetails = "TutorialDetails";
public final static String cSettingNotify = "Notify";
public final static String cSettingLog = "Log";
public final static String cSettingDangerous = "Dangerous";
public final static String cSettingExperimental = "Experimental";
public final static String cSettingRandom = "Random@boot";
public final static String cSettingState = "State";
public final static String cSettingConfidence = "Confidence";
public final static String cSettingHttps = "Https";
public final static String cSettingRegistered = "Registered";
public final static String cSettingUsage = "UsageData";
public final static String cSettingParameters = "Parameters";
public final static String cSettingValues = "Values";
public final static String cSettingSystem = "RestrictSystem";
public final static String cSettingRestricted = "Retricted";
public final static String cSettingOnDemand = "OnDemand";
public final static String cSettingMigrated = "Migrated";
public final static String cSettingCid = "Cid";
public final static String cSettingLac = "Lac";
public final static String cSettingBlacklist = "Blacklist";
public final static String cSettingResolve = "Resolve";
public final static String cSettingNoResolve = "NoResolve";
public final static String cSettingFreeze = "Freeze";
public final static String cSettingPermMan = "PermMan";
public final static String cSettingIntentWall = "IntentWall";
public final static String cSettingSafeMode = "SafeMode";
public final static String cSettingTestVersions = "TestVersions";
public final static String cSettingOnDemandSystem = "OnDemandSystem";
public final static String cSettingLegacy = "Legacy";
public final static String cSettingAOSPMode = "AOSPMode";
public final static String cSettingChangelog = "Changelog";
public final static String cSettingUpdates = "Updates";
public final static String cSettingMethodExpert = "MethodExpert";
public final static String cSettingWhitelistNoModify = "WhitelistNoModify";
public final static String cSettingNoUsageData = "NoUsageData";
public final static String cSettingODExpert = "ODExpert";
public final static String cSettingODCategory = "ODCategory";
public final static String cSettingODOnce = "ODOnce";
public final static String cSettingODOnceDuration = "ODOnceDuration";
// Special value names
public final static String cValueRandom = "#Random#";
public final static String cValueRandomLegacy = "\nRandom\n";
// Constants
public final static int cXposedAppProcessMinVersion = 46;
public final static int cWarnServiceDelayMs = 200;
public final static int cWarnHookDelayMs = 200;
private final static int cMaxExtra = 128;
private final static String cDeface = "DEFACE";
// Caching
public final static int cRestrictionCacheTimeoutMs = 15 * 1000;
public final static int cSettingCacheTimeoutMs = 30 * 1000;
private static Map<String, Map<String, Hook>> mMethod = new LinkedHashMap<String, Map<String, Hook>>();
private static Map<String, List<String>> mRestart = new LinkedHashMap<String, List<String>>();
private static Map<String, List<Hook>> mPermission = new LinkedHashMap<String, List<Hook>>();
private static Map<CSetting, CSetting> mSettingsCache = new HashMap<CSetting, CSetting>();
private static Map<CSetting, CSetting> mTransientCache = new HashMap<CSetting, CSetting>();
private static Map<CRestriction, CRestriction> mRestrictionCache = new HashMap<CRestriction, CRestriction>();
private static SparseArray<Map<String, Boolean>> mPermissionRestrictionCache = new SparseArray<Map<String, Boolean>>();
private static SparseArray<Map<Hook, Boolean>> mPermissionHookCache = new SparseArray<Map<Hook, Boolean>>();
// Meta data
static {
List<Hook> listHook = Meta.get();
List<String> listRestriction = getRestrictions();
for (Hook hook : listHook) {
String restrictionName = hook.getRestrictionName();
if (restrictionName == null)
restrictionName = "";
// Check restriction
else if (!listRestriction.contains(restrictionName))
if (hook.isAvailable())
Util.log(null, Log.WARN, "Not found restriction=" + restrictionName + " hook=" + hook);
// Enlist method
if (!mMethod.containsKey(restrictionName))
mMethod.put(restrictionName, new HashMap<String, Hook>());
mMethod.get(restrictionName).put(hook.getName(), hook);
// Cache restart required methods
if (hook.isRestartRequired()) {
if (!mRestart.containsKey(restrictionName))
mRestart.put(restrictionName, new ArrayList<String>());
mRestart.get(restrictionName).add(hook.getName());
}
// Enlist permissions
String[] permissions = hook.getPermissions();
if (permissions != null)
for (String perm : permissions)
if (!perm.equals("")) {
String aPermission = (perm.contains(".") ? perm : "android.permission." + perm);
if (!mPermission.containsKey(aPermission))
mPermission.put(aPermission, new ArrayList<Hook>());
if (!mPermission.get(aPermission).contains(hook))
mPermission.get(aPermission).add(hook);
}
}
// Util.log(null, Log.WARN, listHook.size() + " hooks");
}
public static List<String> getRestrictions() {
List<String> listRestriction = new ArrayList<String>(Arrays.asList(cRestrictionNames));
if (Hook.isAOSP(19))
listRestriction.remove(cIPC);
return listRestriction;
}
public static TreeMap<String, String> getRestrictions(Context context) {
Collator collator = Collator.getInstance(Locale.getDefault());
TreeMap<String, String> tmRestriction = new TreeMap<String, String>(collator);
for (String restrictionName : getRestrictions()) {
int stringId = context.getResources().getIdentifier("restrict_" + restrictionName, "string",
context.getPackageName());
tmRestriction.put(stringId == 0 ? restrictionName : context.getString(stringId), restrictionName);
}
return tmRestriction;
}
public static Hook getHook(String _restrictionName, String methodName) {
String restrictionName = (_restrictionName == null ? "" : _restrictionName);
if (mMethod.containsKey(restrictionName))
if (mMethod.get(restrictionName).containsKey(methodName))
return mMethod.get(restrictionName).get(methodName);
return null;
}
public static List<Hook> getHooks(String restrictionName, Version version) {
List<Hook> listMethod = new ArrayList<Hook>();
for (String methodName : mMethod.get(restrictionName).keySet()) {
Hook hook = mMethod.get(restrictionName).get(methodName);
if (!hook.isAvailable())
continue;
if (version != null && hook.getFrom() != null && version.compareTo(hook.getFrom()) < 0)
continue;
if ("IntentFirewall".equals(hook.getName()))
if (!PrivacyManager.getSettingBool(0, PrivacyManager.cSettingIntentWall, false))
continue;
if ("checkPermission".equals(hook.getName()) || "checkUidPermission".equals(hook.getName()))
if (!PrivacyManager.getSettingBool(0, PrivacyManager.cSettingPermMan, false))
continue;
listMethod.add(mMethod.get(restrictionName).get(methodName));
}
Collections.sort(listMethod);
return listMethod;
}
public static List<String> getPermissions(String restrictionName, Version version) {
List<String> listPermission = new ArrayList<String>();
for (Hook md : getHooks(restrictionName, version))
if (md.getPermissions() != null)
for (String permission : md.getPermissions())
if (!listPermission.contains(permission))
listPermission.add(permission);
return listPermission;
}
// Restrictions
public static PRestriction getRestrictionEx(int uid, String restrictionName, String methodName) {
PRestriction query = new PRestriction(uid, restrictionName, methodName, false);
PRestriction result = new PRestriction(uid, restrictionName, methodName, false, true);
try {
// Check cache
boolean cached = false;
CRestriction key = new CRestriction(uid, restrictionName, methodName, null);
synchronized (mRestrictionCache) {
if (mRestrictionCache.containsKey(key)) {
CRestriction entry = mRestrictionCache.get(key);
if (!entry.isExpired()) {
cached = true;
result.restricted = entry.restricted;
result.asked = entry.asked;
}
}
}
if (!cached) {
// Get restriction
result = PrivacyService.getRestrictionProxy(query, false, "");
if (result.debug)
Util.logStack(null, Log.WARN);
// Add to cache
key.restricted = result.restricted;
key.asked = result.asked;
if (result.time > 0) {
key.setExpiry(result.time);
Util.log(null, Log.WARN, "Caching " + result + " until " + new Date(result.time));
}
synchronized (mRestrictionCache) {
if (mRestrictionCache.containsKey(key))
mRestrictionCache.remove(key);
mRestrictionCache.put(key, key);
}
}
} catch (RemoteException ex) {
Util.bug(null, ex);
}
return result;
}
public static boolean getRestriction(final XHook hook, int uid, String restrictionName, String methodName,
String secret) {
return getRestrictionExtra(hook, uid, restrictionName, methodName, null, null, secret);
}
public static boolean getRestrictionExtra(final XHook hook, int uid, String restrictionName, String methodName,
String extra, String secret) {
return getRestrictionExtra(hook, uid, restrictionName, methodName, extra, null, secret);
}
public static boolean getRestrictionExtra(final XHook hook, int uid, String restrictionName, String methodName,
String extra, String value, String secret) {
long start = System.currentTimeMillis();
PRestriction result = new PRestriction(uid, restrictionName, methodName, false, true);
// Check uid
if (uid <= 0)
return false;
// Check secret
if (secret == null) {
Util.log(null, Log.ERROR, "Secret missing restriction=" + restrictionName + "/" + methodName);
Util.logStack(hook, Log.ERROR);
secret = "";
}
// Check restriction
if (restrictionName == null || restrictionName.equals("")) {
Util.log(hook, Log.ERROR, "restriction empty method=" + methodName);
Util.logStack(hook, Log.ERROR);
return false;
}
// Check usage
if (methodName == null || methodName.equals("")) {
Util.log(hook, Log.ERROR, "Method empty");
Util.logStack(hook, Log.ERROR);
} else if (getHook(restrictionName, methodName) == null) {
Util.log(hook, Log.ERROR, "Unknown method=" + methodName);
Util.logStack(hook, Log.ERROR);
}
// Check extra
if (extra != null && extra.length() > cMaxExtra)
extra = extra.substring(0, cMaxExtra) + "...";
result.extra = extra;
// Check cache
boolean cached = false;
CRestriction key = new CRestriction(uid, restrictionName, methodName, extra);
synchronized (mRestrictionCache) {
if (mRestrictionCache.containsKey(key)) {
CRestriction entry = mRestrictionCache.get(key);
if (!entry.isExpired()) {
cached = true;
result.restricted = entry.restricted;
result.asked = entry.asked;
}
}
}
// Get restriction
if (!cached)
try {
PRestriction query = new PRestriction(uid, restrictionName, methodName, false);
query.extra = extra;
query.value = value;
PRestriction restriction = PrivacyService.getRestrictionProxy(query, true, secret);
result.restricted = restriction.restricted;
if (restriction.debug)
Util.logStack(null, Log.WARN);
// Add to cache
if (result.time >= 0) {
key.restricted = result.restricted;
key.asked = result.asked;
if (result.time > 0) {
key.setExpiry(result.time);
Util.log(null, Log.WARN, "Caching " + result + " until " + new Date(result.time));
}
synchronized (mRestrictionCache) {
if (mRestrictionCache.containsKey(key))
mRestrictionCache.remove(key);
mRestrictionCache.put(key, key);
}
}
} catch (Throwable ex) {
Util.bug(hook, ex);
}
// Result
long ms = System.currentTimeMillis() - start;
Util.log(hook, ms < cWarnServiceDelayMs ? Log.INFO : Log.WARN,
String.format("Get client %s%s %d ms", result, (cached ? " (cached)" : ""), ms));
return result.restricted;
}
public static void setRestriction(int uid, String restrictionName, String methodName, boolean restricted,
boolean asked) {
checkCaller();
// Check uid
if (uid == 0) {
Util.log(null, Log.WARN, "uid=0");
return;
}
// Build list of restrictions
List<String> listRestriction = new ArrayList<String>();
if (restrictionName == null)
listRestriction.addAll(PrivacyManager.getRestrictions());
else
listRestriction.add(restrictionName);
// Create list of restrictions to set
List<PRestriction> listPRestriction = new ArrayList<PRestriction>();
for (String rRestrictionName : listRestriction)
listPRestriction.add(new PRestriction(uid, rRestrictionName, methodName, restricted, asked));
// Make exceptions
if (methodName == null)
for (String rRestrictionName : listRestriction)
for (Hook md : getHooks(rRestrictionName, null)) {
if (!canRestrict(uid, Process.myUid(), rRestrictionName, md.getName(), false))
listPRestriction.add(new PRestriction(uid, rRestrictionName, md.getName(), false, true));
else if (md.isDangerous())
listPRestriction.add(new PRestriction(uid, rRestrictionName, md.getName(), false, md
.whitelist() == null));
}
setRestrictionList(listPRestriction);
}
public static List<String> cIDCant = Arrays.asList(new String[] { "getString", "Srv_Android_ID", "%serialno",
"SERIAL" });
public static boolean canRestrict(int uid, int xuid, String restrictionName, String methodName, boolean system) {
int _uid = Util.getAppId(uid);
int userId = Util.getUserId(uid);
if (_uid == Process.SYSTEM_UID) {
if (PrivacyManager.cIdentification.equals(restrictionName))
return false;
if (PrivacyManager.cShell.equals(restrictionName) && "loadLibrary".equals(methodName))
return false;
}
if (system)
if (!isApplication(_uid))
if (!getSettingBool(userId, PrivacyManager.cSettingSystem, false))
return false;
// @formatter:off
if (_uid == Util.getAppId(xuid) &&
((PrivacyManager.cIdentification.equals(restrictionName) && cIDCant.contains(methodName))
|| PrivacyManager.cIPC.equals(restrictionName)
|| PrivacyManager.cStorage.equals(restrictionName)
|| PrivacyManager.cSystem.equals(restrictionName)
|| PrivacyManager.cView.equals(restrictionName)))
return false;
// @formatter:on
Hook hook = getHook(restrictionName, methodName);
if (hook != null && hook.isUnsafe())
if (getSettingBool(userId, PrivacyManager.cSettingSafeMode, false))
return false;
return true;
}
public static void updateState(int uid) {
setSetting(uid, cSettingState, Integer.toString(ApplicationInfoEx.STATE_CHANGED));
setSetting(uid, cSettingModifyTime, Long.toString(System.currentTimeMillis()));
}
public static void setRestrictionList(List<PRestriction> listRestriction) {
checkCaller();
if (listRestriction.size() > 0)
try {
PrivacyService.getClient().setRestrictionList(listRestriction);
// Clear cache
synchronized (mRestrictionCache) {
mRestrictionCache.clear();
}
} catch (Throwable ex) {
Util.bug(null, ex);
}
}
public static List<PRestriction> getRestrictionList(int uid, String restrictionName) {
checkCaller();
try {
return PrivacyService.getClient().getRestrictionList(new PRestriction(uid, restrictionName, null, false));
} catch (Throwable ex) {
Util.bug(null, ex);
}
return new ArrayList<PRestriction>();
}
public static boolean isRestrictionSet(PRestriction restriction) {
try {
return PrivacyService.getClient().isRestrictionSet(restriction);
} catch (Throwable ex) {
Util.bug(null, ex);
return false;
}
}
public static void deleteRestrictions(int uid, String restrictionName, boolean deleteWhitelists) {
checkCaller();
try {
// Delete restrictions
PrivacyService.getClient().deleteRestrictions(uid, restrictionName == null ? "" : restrictionName);
// Clear associated whitelists
if (deleteWhitelists && uid > 0) {
for (PSetting setting : getSettingList(uid, null))
if (Meta.isWhitelist(setting.type))
setSetting(uid, setting.type, setting.name, null);
}
// Clear cache
synchronized (mRestrictionCache) {
mRestrictionCache.clear();
}
} catch (Throwable ex) {
Util.bug(null, ex);
}
// Mark as new/changed
setSetting(uid, cSettingState, Integer.toString(restrictionName == null ? ApplicationInfoEx.STATE_CHANGED
: ApplicationInfoEx.STATE_ATTENTION));
// Change app modification time
setSetting(uid, cSettingModifyTime, Long.toString(System.currentTimeMillis()));
}
public static List<Boolean> getRestartStates(int uid, String restrictionName) {
// Returns a list of restriction states for functions whose application
// requires the app to be restarted.
List<Boolean> listRestartRestriction = new ArrayList<Boolean>();
Set<String> listRestriction = new HashSet<String>();
if (restrictionName == null)
listRestriction = mRestart.keySet();
else if (mRestart.keySet().contains(restrictionName))
listRestriction.add(restrictionName);
try {
for (String restriction : listRestriction) {
for (String method : mRestart.get(restriction))
listRestartRestriction.add(getRestrictionEx(uid, restriction, method).restricted);
}
} catch (Throwable ex) {
Util.bug(null, ex);
}
return listRestartRestriction;
}
public static void applyTemplate(int uid, String templateName, String restrictionName, boolean methods,
boolean clear, boolean invert) {
checkCaller();
int userId = Util.getUserId(uid);
// Check on-demand
boolean ondemand = getSettingBool(userId, PrivacyManager.cSettingOnDemand, true);
// Build list of restrictions
List<String> listRestriction = new ArrayList<String>();
if (restrictionName == null)
listRestriction.addAll(getRestrictions());
else
listRestriction.add(restrictionName);
// Apply template
Util.log(null, Log.WARN, "Applying template=" + templateName);
boolean hasOndemand = false;
List<PRestriction> listPRestriction = new ArrayList<PRestriction>();
for (String rRestrictionName : listRestriction) {
// Cleanup
if (clear)
deleteRestrictions(uid, rRestrictionName, false);
// Parent
String parentValue = getSetting(userId, templateName, rRestrictionName, Boolean.toString(!ondemand)
+ "+ask");
boolean parentRestricted = parentValue.contains("true");
boolean parentAsked = (!ondemand || parentValue.contains("asked"));
hasOndemand = hasOndemand || !parentAsked;
// Merge
PRestriction parentMerge;
if (clear)
parentMerge = new PRestriction(uid, rRestrictionName, null, parentRestricted, parentAsked);
else
parentMerge = getRestrictionEx(uid, rRestrictionName, null);
// Apply
if (canRestrict(uid, Process.myUid(), rRestrictionName, null, true))
if (invert && ((parentRestricted && parentMerge.restricted) || (!parentAsked && !parentMerge.asked))) {
listPRestriction.add(new PRestriction(uid, rRestrictionName, null, parentRestricted ? false
: parentMerge.restricted, !parentAsked ? true : parentMerge.asked));
continue; // leave functions
} else
listPRestriction.add(new PRestriction(uid, rRestrictionName, null, parentMerge.restricted
|| parentRestricted, parentMerge.asked && parentAsked));
// Childs
if (methods)
for (Hook hook : getHooks(rRestrictionName, null))
if (canRestrict(uid, Process.myUid(), rRestrictionName, hook.getName(), true)) {
// Child
String settingName = rRestrictionName + "." + hook.getName();
String childValue = getSetting(userId, templateName, settingName, null);
if (childValue == null)
childValue = Boolean.toString(parentRestricted && !hook.isDangerous())
+ (parentAsked || (hook.isDangerous() && hook.whitelist() == null) ? "+asked"
: "+ask");
boolean restricted = childValue.contains("true");
boolean asked = (!ondemand || childValue.contains("asked"));
// Merge
PRestriction childMerge;
if (clear)
childMerge = new PRestriction(uid, rRestrictionName, hook.getName(), parentRestricted
&& restricted, parentAsked || asked);
else
childMerge = getRestrictionEx(uid, rRestrictionName, hook.getName());
// Invert
if (invert && parentRestricted && restricted) {
restricted = false;
childMerge.restricted = false;
}
if (invert && !parentAsked && !asked) {
asked = true;
childMerge.asked = true;
}
// Apply
if ((parentRestricted && !restricted) || (!parentAsked && asked)
|| (invert ? false : hook.isDangerous() || !clear)) {
PRestriction child = new PRestriction(uid, rRestrictionName, hook.getName(),
(parentRestricted && restricted) || childMerge.restricted, (parentAsked || asked)
&& childMerge.asked);
listPRestriction.add(child);
}
}
}
// Apply result
setRestrictionList(listPRestriction);
if (hasOndemand)
PrivacyManager.setSetting(uid, PrivacyManager.cSettingOnDemand, Boolean.toString(true));
}
// White/black listing
public static Map<String, TreeMap<String, Boolean>> listWhitelisted(int uid, String type) {
checkCaller();
Map<String, TreeMap<String, Boolean>> mapWhitelisted = new HashMap<String, TreeMap<String, Boolean>>();
for (PSetting setting : getSettingList(uid, type))
if (Meta.isWhitelist(setting.type)) {
if (!mapWhitelisted.containsKey(setting.type))
mapWhitelisted.put(setting.type, new TreeMap<String, Boolean>());
mapWhitelisted.get(setting.type).put(setting.name, Boolean.parseBoolean(setting.value));
}
return mapWhitelisted;
}
// Usage
public static long getUsage(int uid, String restrictionName, String methodName) {
checkCaller();
try {
List<PRestriction> listRestriction = new ArrayList<PRestriction>();
if (restrictionName == null)
for (String sRestrictionName : getRestrictions())
listRestriction.add(new PRestriction(uid, sRestrictionName, methodName, false));
else
listRestriction.add(new PRestriction(uid, restrictionName, methodName, false));
return PrivacyService.getClient().getUsage(listRestriction);
} catch (Throwable ex) {
Util.bug(null, ex);
return 0;
}
}
public static List<PRestriction> getUsageList(Context context, int uid, String restrictionName) {
checkCaller();
List<PRestriction> listUsage = new ArrayList<PRestriction>();
try {
listUsage.addAll(PrivacyService.getClient().getUsageList(uid,
restrictionName == null ? "" : restrictionName));
} catch (Throwable ex) {
Util.log(null, Log.ERROR, "getUsageList");
Util.bug(null, ex);
}
Collections.sort(listUsage, new ParcelableRestrictionCompare());
return listUsage;
}
public static class ParcelableRestrictionCompare implements Comparator<PRestriction> {
@Override
public int compare(PRestriction one, PRestriction another) {
if (one.time < another.time)
return 1;
else if (one.time > another.time)
return -1;
else
return 0;
}
}
public static void deleteUsage(int uid) {
checkCaller();
try {
PrivacyService.getClient().deleteUsage(uid);
} catch (Throwable ex) {
Util.bug(null, ex);
}
}
// Settings
public static String getSalt(int userId) {
String def = (Build.SERIAL == null ? "" : Build.SERIAL);
return getSetting(userId, cSettingSalt, def);
}
public static void removeLegacySalt(int userId) {
String def = (Build.SERIAL == null ? "" : Build.SERIAL);
String salt = getSetting(userId, cSettingSalt, null);
if (def.equals(salt))
setSetting(userId, cSettingSalt, null);
}
public static boolean getSettingBool(int uid, String name, boolean defaultValue) {
return Boolean.parseBoolean(getSetting(uid, name, Boolean.toString(defaultValue)));
}
public static boolean getSettingBool(int uid, String type, String name, boolean defaultValue) {
return Boolean.parseBoolean(getSetting(uid, type, name, Boolean.toString(defaultValue)));
}
public static String getSetting(int uid, String name, String defaultValue) {
return getSetting(uid, "", name, defaultValue);
}
public static String getSetting(int uid, String type, String name, String defaultValue) {
long start = System.currentTimeMillis();
String value = null;
// Check cache
boolean cached = false;
boolean willExpire = false;
CSetting key = new CSetting(uid, type, name);
synchronized (mSettingsCache) {
if (mSettingsCache.containsKey(key)) {
CSetting entry = mSettingsCache.get(key);
if (!entry.isExpired()) {
cached = true;
value = entry.getValue();
willExpire = entry.willExpire();
}
}
}
// Get settings
if (!cached)
try {
value = PrivacyService.getSettingProxy(new PSetting(Math.abs(uid), type, name, null)).value;
if (value == null)
if (uid > 99) {
int userId = Util.getUserId(uid);
value = PrivacyService.getSettingProxy(new PSetting(userId, type, name, null)).value;
}
// Add to cache
if (value == null)
key.setValue(defaultValue);
else
key.setValue(value);
synchronized (mSettingsCache) {
if (mSettingsCache.containsKey(key))
mSettingsCache.remove(key);
mSettingsCache.put(key, key);
}
} catch (Throwable ex) {
Util.bug(null, ex);
}
if (value == null)
value = defaultValue;
long ms = System.currentTimeMillis() - start;
if (!willExpire && !cSettingLog.equals(name))
Util.log(null, ms < cWarnServiceDelayMs ? Log.INFO : Log.WARN, String.format(
"Get setting uid=%d %s/%s=%s%s %d ms", uid, type, name, value, (cached ? " (cached)" : ""), ms));
return value;
}
public static void setSetting(int uid, String name, String value) {
setSetting(uid, "", name, value);
}
public static void setSetting(int uid, String type, String name, String value) {
checkCaller();
try {
PrivacyService.getClient().setSetting(new PSetting(uid, type, name, value));
// Update cache
CSetting key = new CSetting(uid, type, name);
key.setValue(value);
synchronized (mSettingsCache) {
if (mSettingsCache.containsKey(key))
mSettingsCache.remove(key);
mSettingsCache.put(key, key);
}
} catch (Throwable ex) {
Util.bug(null, ex);
}
}
public static void setSettingList(List<PSetting> listSetting) {
checkCaller();
if (listSetting.size() > 0)
try {
PrivacyService.getClient().setSettingList(listSetting);
// Clear cache
synchronized (mSettingsCache) {
mSettingsCache.clear();
}
} catch (Throwable ex) {
Util.bug(null, ex);
}
}
public static List<PSetting> getSettingList(int uid, String type) {
checkCaller();
try {
return PrivacyService.getClient().getSettingList(new PSetting(uid, type, null, null));
} catch (Throwable ex) {
Util.bug(null, ex);
}
return new ArrayList<PSetting>();
}
public static void deleteSettings(int uid) {
checkCaller();
try {
PrivacyService.getClient().deleteSettings(uid);
// Clear cache
synchronized (mSettingsCache) {
mSettingsCache.clear();
}
} catch (Throwable ex) {
Util.bug(null, ex);
}
}
private static final List<String> cSettingAppSpecific = Arrays.asList(new String[] { cSettingRandom,
cSettingSerial, cSettingLatitude, cSettingLongitude, cSettingAltitude, cSettingMac, cSettingIP,
cSettingImei, cSettingPhone, cSettingId, cSettingGsfId, cSettingAdId, cSettingMcc, cSettingMnc,
cSettingCountry, cSettingOperator, cSettingIccId, cSettingCid, cSettingLac, cSettingSubscriber,
cSettingSSID, cSettingUa });
public static boolean hasSpecificSettings(int uid) {
boolean specific = false;
for (PSetting setting : getSettingList(uid, ""))
if (cSettingAppSpecific.contains(setting.name)) {
specific = true;
break;
}
return specific;
}
public static String getTransient(String name, String defaultValue) {
CSetting csetting = new CSetting(0, "", name);
synchronized (mTransientCache) {
if (mTransientCache.containsKey(csetting))
return mTransientCache.get(csetting).getValue();
}
return defaultValue;
}
public static void setTransient(String name, String value) {
CSetting setting = new CSetting(0, "", name);
setting.setValue(value);
synchronized (mTransientCache) {
mTransientCache.put(setting, setting);
}
}
// Common
public static void clear() {
checkCaller();
try {
PrivacyService.getClient().clear();
flush();
} catch (Throwable ex) {
Util.bug(null, ex);
}
}
public static void flush() {
synchronized (mSettingsCache) {
mSettingsCache.clear();
}
synchronized (mRestrictionCache) {
mRestrictionCache.clear();
}
synchronized (mPermissionRestrictionCache) {
mPermissionRestrictionCache.clear();
}
synchronized (mPermissionHookCache) {
mPermissionHookCache.clear();
}
}
// Defacing
@SuppressLint("DefaultLocale")
public static Object getDefacedProp(int uid, String name) {
// Serial number
if (name.equals("SERIAL") || name.equals("%serialno")) {
String value = getSetting(uid, cSettingSerial, cDeface);
return (cValueRandom.equals(value) ? getRandomProp("SERIAL") : value);
}
// Host name
if (name.equals("%hostname"))
return cDeface;
// MAC addresses
if (name.equals("MAC") || name.equals("%macaddr")) {
String mac = getSetting(uid, cSettingMac, "DE:FA:CE:DE:FA:CE");
if (cValueRandom.equals(mac))
return getRandomProp("MAC");
StringBuilder sb = new StringBuilder(mac.replace(":", ""));
while (sb.length() != 12)
sb.insert(0, '0');
while (sb.length() > 12)
sb.deleteCharAt(sb.length() - 1);
for (int i = 10; i > 0; i -= 2)
sb.insert(i, ':');
return sb.toString();
}
// cid
if (name.equals("%cid"))
return cDeface;
// IMEI
if (name.equals("getDeviceId") || name.equals("%imei")) {
String value = getSetting(uid, cSettingImei, "000000000000000");
return (cValueRandom.equals(value) ? getRandomProp("IMEI") : value);
}
// Phone
if (name.equals("PhoneNumber") || name.equals("getLine1AlphaTag") || name.equals("getLine1Number")
|| name.equals("getMsisdn") || name.equals("getVoiceMailAlphaTag") || name.equals("getVoiceMailNumber")
|| name.equals("getCompleteVoiceMailNumber")) {
String value = getSetting(uid, cSettingPhone, cDeface);
return (cValueRandom.equals(value) ? getRandomProp("PHONE") : value);
}
// Android ID
if (name.equals("ANDROID_ID")) {
String value = getSetting(uid, cSettingId, cDeface);
return (cValueRandom.equals(value) ? getRandomProp("ANDROID_ID") : value);
}
// Telephony manager
if (name.equals("getGroupIdLevel1"))
return null;
if (name.equals("getIsimDomain"))
return null;
if (name.equals("getIsimImpi"))
return null;
if (name.equals("getIsimImpu"))
return null;
if (name.equals("getNetworkCountryIso") || name.equals("CountryIso")) {
// ISO country code
String value = getSetting(uid, cSettingCountry, "XX");
return (cValueRandom.equals(value) ? getRandomProp("ISO3166") : value);
}
if (name.equals("getNetworkOperator"))
// MCC+MNC: test network
return getSetting(uid, cSettingMcc, "001") + getSetting(uid, cSettingMnc, "01");
if (name.equals("getNetworkOperatorName"))
return getSetting(uid, cSettingOperator, cDeface);
if (name.equals("getSimCountryIso")) {
// ISO country code
String value = getSetting(uid, cSettingCountry, "XX");
return (cValueRandom.equals(value) ? getRandomProp("ISO3166") : value);
}
if (name.equals("getSimOperator"))
// MCC+MNC: test network
return getSetting(uid, cSettingMcc, "001") + getSetting(uid, cSettingMnc, "01");
if (name.equals("getSimOperatorName"))
return getSetting(uid, cSettingOperator, cDeface);
if (name.equals("getSimSerialNumber") || name.equals("getIccSerialNumber") || name.equals("getIccSerialNumber"))
return getSetting(uid, cSettingIccId, null);
if (name.equals("getSubscriberId")) { // IMSI for a GSM phone
String value = getSetting(uid, cSettingSubscriber, null);
return (cValueRandom.equals(value) ? getRandomProp("SubscriberId") : value);
}
if (name.equals("SSID")) {
// Default hidden network
String value = getSetting(uid, cSettingSSID, "");
return (cValueRandom.equals(value) ? getRandomProp("SSID") : value);
}
// Google services framework ID
if (name.equals("GSF_ID")) {
long gsfid = 0xDEFACE;
try {
String value = getSetting(uid, cSettingGsfId, "DEFACE");
if (cValueRandom.equals(value))
value = getRandomProp(name);
gsfid = Long.parseLong(value.toLowerCase(), 16);
} catch (Throwable ignored) {
}
return gsfid;
}
// Advertisement ID
if (name.equals("AdvertisingId")) {
String adid = getSetting(uid, cSettingAdId, "DEFACE00-0000-0000-0000-000000000000");
if (cValueRandom.equals(adid))
adid = getRandomProp(name);
return adid;
}
if (name.equals("InetAddress")) {
// Set address
String ip = getSetting(uid, cSettingIP, null);
if (ip != null)
try {
return InetAddress.getByName(ip);
} catch (Throwable ignored) {
}
// Any address (0.0.0.0)
try {
Field unspecified = Inet4Address.class.getDeclaredField("ANY");
unspecified.setAccessible(true);
return (InetAddress) unspecified.get(Inet4Address.class);
} catch (Throwable ex) {
Util.bug(null, ex);
return null;
}
}
if (name.equals("IPInt")) {
// Set address
String ip = getSetting(uid, cSettingIP, null);
if (ip != null)
try {
InetAddress inet = InetAddress.getByName(ip);
if (inet.getClass().equals(Inet4Address.class)) {
byte[] b = inet.getAddress();
return b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24);
}
} catch (Throwable ex) {
Util.bug(null, ex);
}
// Any address (0.0.0.0)
return 0;
}
if (name.equals("Bytes3"))
return new byte[] { (byte) 0xDE, (byte) 0xFA, (byte) 0xCE };
if (name.equals("UA"))
return getSetting(uid, cSettingUa,
"Mozilla/5.0 (Linux; U; Android; en-us) AppleWebKit/999+ (KHTML, like Gecko) Safari/999.9");
// InputDevice
if (name.equals("DeviceDescriptor"))
return cDeface;
// getExtraInfo
if (name.equals("ExtraInfo"))
return cDeface;
if (name.equals("MCC"))
return getSetting(uid, cSettingMcc, "001");
if (name.equals("MNC"))
return getSetting(uid, cSettingMnc, "01");
if (name.equals("CID"))
try {
return Integer.parseInt(getSetting(uid, cSettingCid, "0")) & 0xFFFF;
} catch (Throwable ignored) {
return -1;
}
if (name.equals("LAC"))
try {
return Integer.parseInt(getSetting(uid, cSettingLac, "0")) & 0xFFFF;
} catch (Throwable ignored) {
return -1;
}
if (name.equals("USB"))
return cDeface;
if (name.equals("BTName"))
return cDeface;
if (name.equals("CastID"))
return cDeface;
// Fallback
Util.log(null, Log.ERROR, "Fallback value name=" + name);
Util.logStack(null, Log.ERROR);
return cDeface;
}
public static Location getDefacedLocation(int uid, Location location) {
// Christmas Island ~ -10.5 / 105.667
String sLat = getSetting(uid, cSettingLatitude, "-10.5");
String sLon = getSetting(uid, cSettingLongitude, "105.667");
String sAlt = getSetting(uid, cSettingAltitude, "686");
// Backward compatibility
if ("".equals(sLat))
sLat = "-10.5";
if ("".equals(sLon))
sLon = "105.667";
if (cValueRandom.equals(sLat))
sLat = getRandomProp("LAT");
if (cValueRandom.equals(sLon))
sLon = getRandomProp("LON");
if (cValueRandom.equals(sAlt))
sAlt = getRandomProp("ALT");
// 1 degree ~ 111111 m
// 1 m ~ 0,000009 degrees
if (location == null)
location = new Location(cDeface);
location.setLatitude(Float.parseFloat(sLat) + (Math.random() * 2.0 - 1.0) * location.getAccuracy() * 9e-6);
location.setLongitude(Float.parseFloat(sLon) + (Math.random() * 2.0 - 1.0) * location.getAccuracy() * 9e-6);
location.setAltitude(Float.parseFloat(sAlt) + (Math.random() * 2.0 - 1.0) * location.getAccuracy());
return location;
}
@SuppressLint("DefaultLocale")
public static String getRandomProp(String name) {
Random r = new Random();
if (name.equals("SERIAL")) {
long v = r.nextLong();
return Long.toHexString(v).toUpperCase();
}
if (name.equals("MAC")) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 6; i++) {
if (i != 0)
sb.append(':');
int v = r.nextInt(256);
if (i == 0)
v = v & 0xFC; // unicast, globally unique
sb.append(Integer.toHexString(0x100 | v).substring(1));
}
return sb.toString().toUpperCase();
}
// IMEI/MEID
if (name.equals("IMEI")) {
// http://en.wikipedia.org/wiki/Reporting_Body_Identifier
String[] rbi = new String[] { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "30", "33",
"35", "44", "45", "49", "50", "51", "52", "53", "54", "86", "91", "98", "99" };
String imei = rbi[r.nextInt(rbi.length)];
while (imei.length() < 14)
imei += Character.forDigit(r.nextInt(10), 10);
imei += getLuhnDigit(imei);
return imei;
}
if (name.equals("PHONE")) {
String phone = "0";
for (int i = 1; i < 10; i++)
phone += Character.forDigit(r.nextInt(10), 10);
return phone;
}
if (name.equals("ANDROID_ID")) {
long v = r.nextLong();
return Long.toHexString(v);
}
if (name.equals("ISO3166")) {
String letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
String country = Character.toString(letters.charAt(r.nextInt(letters.length())))
+ Character.toString(letters.charAt(r.nextInt(letters.length())));
return country;
}
if (name.equals("GSF_ID")) {
long v = Math.abs(r.nextLong());
return Long.toString(v, 16).toUpperCase();
}
if (name.equals("AdvertisingId"))
return UUID.randomUUID().toString().toUpperCase();
if (name.equals("LAT")) {
double d = r.nextDouble() * 180 - 90;
d = Math.rint(d * 1e7) / 1e7;
return Double.toString(d);
}
if (name.equals("LON")) {
double d = r.nextDouble() * 360 - 180;
d = Math.rint(d * 1e7) / 1e7;
return Double.toString(d);
}
if (name.equals("ALT")) {
double d = r.nextDouble() * 2 * 686;
return Double.toString(d);
}
if (name.equals("SubscriberId")) {
String subscriber = "00101";
while (subscriber.length() < 15)
subscriber += Character.forDigit(r.nextInt(10), 10);
return subscriber;
}
if (name.equals("SSID")) {
String ssid = "";
while (ssid.length() < 6)
ssid += (char) (r.nextInt(26) + 'A');
ssid += Character.forDigit(r.nextInt(10), 10);
ssid += Character.forDigit(r.nextInt(10), 10);
return ssid;
}
return "";
}
private static char getLuhnDigit(String x) {
// http://en.wikipedia.org/wiki/Luhn_algorithm
int sum = 0;
for (int i = 0; i < x.length(); i++) {
int n = Character.digit(x.charAt(x.length() - 1 - i), 10);
if (i % 2 == 0) {
n *= 2;
if (n > 9)
n -= 9; // n = (n % 10) + 1;
}
sum += n;
}
return Character.forDigit((sum * 9) % 10, 10);
}
// Helper methods
public static void checkCaller() {
if (PrivacyService.isRegistered()) {
Util.log(null, Log.ERROR, "Privacy manager call from service");
Util.logStack(null, Log.ERROR);
}
}
public static final int FIRST_ISOLATED_UID = 99000;
public static final int LAST_ISOLATED_UID = 99999;
public static final int FIRST_SHARED_APPLICATION_GID = 50000;
public static final int LAST_SHARED_APPLICATION_GID = 59999;
public static boolean isApplication(int uid) {
uid = Util.getAppId(uid);
return (uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID);
}
public static boolean isShared(int uid) {
uid = Util.getAppId(uid);
return (uid >= FIRST_SHARED_APPLICATION_GID && uid <= LAST_SHARED_APPLICATION_GID);
}
public static boolean isIsolated(int uid) {
uid = Util.getAppId(uid);
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID);
}
public static boolean hasPermission(Context context, ApplicationInfoEx appInfo, String restrictionName,
Version version) {
int uid = appInfo.getUid();
synchronized (mPermissionRestrictionCache) {
if (mPermissionRestrictionCache.get(uid) == null)
mPermissionRestrictionCache.append(uid, new HashMap<String, Boolean>());
if (!mPermissionRestrictionCache.get(uid).containsKey(restrictionName)) {
boolean permission = hasPermission(context, appInfo.getPackageName(),
getPermissions(restrictionName, version));
mPermissionRestrictionCache.get(uid).put(restrictionName, permission);
}
return mPermissionRestrictionCache.get(uid).get(restrictionName);
}
}
public static boolean hasPermission(Context context, ApplicationInfoEx appInfo, Hook md) {
int uid = appInfo.getUid();
synchronized (mPermissionHookCache) {
if (mPermissionHookCache.get(uid) == null)
mPermissionHookCache.append(uid, new HashMap<Hook, Boolean>());
if (!mPermissionHookCache.get(uid).containsKey(md)) {
List<String> listPermission = (md.getPermissions() == null ? null : Arrays.asList(md.getPermissions()));
boolean permission = hasPermission(context, appInfo.getPackageName(), listPermission);
mPermissionHookCache.get(uid).put(md, permission);
}
return mPermissionHookCache.get(uid).get(md);
}
}
public static void clearPermissionCache(int uid) {
synchronized (mPermissionRestrictionCache) {
if (mPermissionRestrictionCache.get(uid) != null)
mPermissionRestrictionCache.remove(uid);
}
synchronized (mPermissionHookCache) {
if (mPermissionHookCache.get(uid) != null)
mPermissionHookCache.remove(uid);
}
}
@SuppressLint("DefaultLocale")
private static boolean hasPermission(Context context, List<String> listPackage, List<String> listPermission) {
try {
if (listPermission == null || listPermission.size() == 0 || listPermission.contains(""))
return true;
PackageManager pm = context.getPackageManager();
for (String packageName : listPackage) {
// Check absolute permissions
for (String apermission : listPermission)
if (apermission.contains("."))
if (pm.checkPermission(apermission, packageName) == PackageManager.PERMISSION_GRANTED)
return true;
// Check relative permissions
PackageInfo pInfo = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
if (pInfo != null && pInfo.requestedPermissions != null)
for (String rPermission : pInfo.requestedPermissions)
for (String permission : listPermission)
if (rPermission.toLowerCase().contains(permission.toLowerCase())) {
String aPermission = "android.permission." + permission;
if (!aPermission.equals(rPermission))
Util.log(null, Log.WARN, "Check permission=" + permission + "/" + rPermission);
return true;
}
}
} catch (NameNotFoundException ex) {
Util.log(null, Log.WARN, ex.toString());
} catch (Throwable ex) {
Util.bug(null, ex);
}
return false;
}
}