package biz.bokhorst.xprivacy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.net.InterfaceAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.location.GpsStatus;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
public class Requirements {
private static String[] cIncompatible = new String[] { "com.lbe.security" };
@SuppressWarnings("unchecked")
public static void check(final ActivityBase context) {
// Check Android version
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setTitle(R.string.app_name);
alertDialogBuilder.setMessage(R.string.app_wrongandroid);
alertDialogBuilder.setIcon(context.getThemed(R.attr.icon_launcher));
alertDialogBuilder.setPositiveButton(context.getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent androidIntent = new Intent(Intent.ACTION_VIEW);
androidIntent.setData(Uri.parse("https://github.com/M66B/XPrivacy#installation"));
context.startActivity(androidIntent);
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
// Check if XPrivacy is enabled
if (Util.isXposedEnabled()) {
// Check privacy client
try {
if (PrivacyService.checkClient()) {
List<String> listError = (List<String>) PrivacyService.getClient().check();
if (listError.size() > 0)
sendSupportInfo(TextUtils.join("\r\n", listError), context);
}
} catch (Throwable ex) {
sendSupportInfo(ex.toString(), context);
}
} else {
// @formatter:off
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setTitle(R.string.app_name);
alertDialogBuilder.setMessage(R.string.app_notenabled);
alertDialogBuilder.setIcon(context.getThemed(R.attr.icon_launcher));
alertDialogBuilder.setPositiveButton(context.getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent xInstallerIntent = new Intent("de.robv.android.xposed.installer.OPEN_SECTION")
.setPackage("de.robv.android.xposed.installer")
.putExtra("section", "modules")
.putExtra("module", context.getPackageName())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(xInstallerIntent);
}
});
// @formatter:on
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
// Check pro enabler
Version version = Util.getProEnablerVersion(context);
if (version != null && !Util.isValidProEnablerVersion(version)) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setTitle(R.string.app_name);
alertDialogBuilder.setMessage(R.string.app_wrongenabler);
alertDialogBuilder.setIcon(context.getThemed(R.attr.icon_launcher));
alertDialogBuilder.setPositiveButton(context.getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent storeIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id="
+ context.getPackageName() + ".pro"));
context.startActivity(storeIntent);
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
// Check incompatible apps
checkCompatibility(context);
// Check activity thread
try {
Class<?> clazz = Class.forName("android.app.ActivityThread", false, null);
try {
clazz.getDeclaredMethod("unscheduleGcIdler");
} catch (NoSuchMethodException ex) {
reportClass(clazz, context);
}
} catch (ClassNotFoundException ex) {
sendSupportInfo(ex.toString(), context);
}
// Check activity thread receiver data
try {
Class<?> clazz = Class.forName("android.app.ActivityThread$ReceiverData", false, null);
if (!checkField(clazz, "intent"))
reportClass(clazz, context);
} catch (ClassNotFoundException ex) {
try {
reportClass(Class.forName("android.app.ActivityThread", false, null), context);
} catch (ClassNotFoundException exex) {
sendSupportInfo(exex.toString(), context);
}
}
// Check file utils
try {
Class<?> clazz = Class.forName("android.os.FileUtils", false, null);
try {
clazz.getDeclaredMethod("setPermissions", String.class, int.class, int.class, int.class);
} catch (NoSuchMethodException ex) {
reportClass(clazz, context);
}
} catch (ClassNotFoundException ex) {
sendSupportInfo(ex.toString(), context);
}
// Check interface address
if (!checkField(InterfaceAddress.class, "address") || !checkField(InterfaceAddress.class, "broadcastAddress")
|| (PrivacyService.getClient() != null && PrivacyManager.getDefacedProp(0, "InetAddress") == null))
reportClass(InterfaceAddress.class, context);
// Check package manager service
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
try {
Class<?> clazz = Class.forName("com.android.server.pm.PackageManagerService", false, null);
try {
try {
clazz.getDeclaredMethod("getPackageUid", String.class, int.class);
} catch (NoSuchMethodException ignored) {
clazz.getDeclaredMethod("getPackageUid", String.class);
}
} catch (NoSuchMethodException ex) {
reportClass(clazz, context);
}
} catch (ClassNotFoundException ex) {
sendSupportInfo(ex.toString(), context);
}
// Check GPS status
if (!checkField(GpsStatus.class, "mSatellites"))
reportClass(GpsStatus.class, context);
// Check service manager
try {
Class<?> clazz = Class.forName("android.os.ServiceManager", false, null);
try {
// @formatter:off
// public static void addService(String name, IBinder service)
// public static void addService(String name, IBinder service, boolean allowIsolated)
// public static String[] listServices()
// public static IBinder checkService(String name)
// @formatter:on
Method listServices = clazz.getDeclaredMethod("listServices");
Method getService = clazz.getDeclaredMethod("getService", String.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
clazz.getDeclaredMethod("addService", String.class, IBinder.class, boolean.class);
else
clazz.getDeclaredMethod("addService", String.class, IBinder.class);
// Get services
Map<String, String> mapService = new HashMap<String, String>();
String[] services = (String[]) listServices.invoke(null);
if (services != null)
for (String service : services)
if (service != null) {
IBinder binder = (IBinder) getService.invoke(null, service);
String descriptor = (binder == null ? null : binder.getInterfaceDescriptor());
mapService.put(service, descriptor);
}
if (mapService.size() > 0) {
// Check services
int i = 0;
List<String> listMissing = new ArrayList<String>();
for (String name : XBinder.cServiceName) {
String descriptor = XBinder.cServiceDescriptor.get(i++);
if (descriptor != null && !XBinder.cServiceOptional.contains(name)) {
// Check name
boolean checkDescriptor = false;
if (name.equals("telephony.registry")) {
if (mapService.containsKey(name))
checkDescriptor = true;
else if (!mapService.containsKey("telephony.msim.registry"))
listMissing.add(name);
} else if (name.equals("telephony.msim.registry")) {
if (mapService.containsKey(name))
checkDescriptor = true;
else if (!mapService.containsKey("telephony.registry"))
listMissing.add(name);
} else if (name.equals("bluetooth")) {
if (mapService.containsKey(name))
checkDescriptor = true;
else if (!mapService.containsKey("bluetooth_manager"))
listMissing.add(name);
} else if (name.equals("bluetooth_manager")) {
if (mapService.containsKey(name))
checkDescriptor = true;
else if (!mapService.containsKey("bluetooth"))
listMissing.add(name);
} else {
if (mapService.containsKey(name))
checkDescriptor = true;
else
listMissing.add(name);
}
// Check descriptor
if (checkDescriptor) {
String d = mapService.get(name);
if (d != null && !d.equals(descriptor))
listMissing.add(descriptor);
}
}
}
// Check result
if (listMissing.size() > 0) {
List<String> listService = new ArrayList<String>();
for (String service : mapService.keySet())
listService.add(String.format("%s: %s", service, mapService.get(service)));
sendSupportInfo("Missing:\r\n" + TextUtils.join("\r\n", listMissing) + "\r\n\r\nAvailable:\r\n"
+ TextUtils.join("\r\n", listService), context);
}
}
} catch (NoSuchMethodException ex) {
reportClass(clazz, context);
} catch (Throwable ex) {
Util.bug(null, ex);
}
} catch (ClassNotFoundException ex) {
sendSupportInfo(ex.toString(), context);
}
// Check wifi info
if (!checkField(WifiInfo.class, "mSupplicantState") || !checkField(WifiInfo.class, "mBSSID")
|| !checkField(WifiInfo.class, "mIpAddress") || !checkField(WifiInfo.class, "mMacAddress")
|| !(checkField(WifiInfo.class, "mSSID") || checkField(WifiInfo.class, "mWifiSsid")))
reportClass(WifiInfo.class, context);
// Check mWifiSsid.octets
if (checkField(WifiInfo.class, "mWifiSsid"))
try {
Class<?> clazz = Class.forName("android.net.wifi.WifiSsid", false, null);
try {
clazz.getDeclaredMethod("createFromAsciiEncoded", String.class);
} catch (NoSuchMethodException ex) {
reportClass(clazz, context);
}
} catch (ClassNotFoundException ex) {
sendSupportInfo(ex.toString(), context);
}
// Check Inet4Address/ANY
try {
Inet4Address.class.getDeclaredField("ANY");
} catch (Throwable ex) {
reportClass(Inet4Address.class, context);
}
// Check context services
checkService(context, Context.ACCOUNT_SERVICE,
new String[] { "android.accounts.AccountManager", "com.intel.arkham.ExtendAccountManager" /* Asus */,
"android.privacy.surrogate.PrivacyAccountManager" /* PDroid */});
checkService(context, Context.ACTIVITY_SERVICE, new String[] { "android.app.ActivityManager",
"android.app.ActivityManagerEx" });
checkService(context, Context.CLIPBOARD_SERVICE, new String[] { "android.content.ClipboardManager" });
checkService(context, Context.CONNECTIVITY_SERVICE, new String[] { "android.net.ConnectivityManager",
"android.net.ConnectivityManagerEx", "android.net.MultiSimConnectivityManager",
"android.privacy.surrogate.PrivacyConnectivityManager" /* PDroid */});
checkService(context, Context.LOCATION_SERVICE,
new String[] { "android.location.LocationManager", "android.location.ZTEPrivacyLocationManager",
"android.privacy.surrogate.PrivacyLocationManager" /* PDroid */});
Class<?> serviceClass = context.getPackageManager().getClass();
if (!"android.app.ApplicationPackageManager".equals(serviceClass.getName())
&& !"amazon.content.pm.AmazonPackageManagerImpl".equals(serviceClass.getName()))
reportClass(serviceClass, context);
checkService(context, Context.SENSOR_SERVICE, new String[] { "android.hardware.SensorManager",
"android.hardware.SystemSensorManager" });
checkService(context, Context.TELEPHONY_SERVICE, new String[] { "android.telephony.TelephonyManager",
"android.telephony.MSimTelephonyManager", "android.telephony.MultiSimTelephonyManager",
"android.telephony.ZTEPrivacyTelephonyManager", "android.telephony.ZTEPrivacyMSimTelephonyManager",
"com.motorola.android.telephony.MotoTelephonyManager",
"android.privacy.surrogate.PrivacyTelephonyManager" /* PDroid */});
checkService(context, Context.WINDOW_SERVICE, new String[] { "android.view.WindowManagerImpl",
"android.view.Window$LocalWindowManager", "amazon.view.AmazonWindowManagerImpl" });
checkService(context, Context.WIFI_SERVICE, new String[] { "android.net.wifi.WifiManager",
"com.amazon.net.AmazonWifiManager", "com.amazon.android.service.AmazonWifiManager",
"android.privacy.surrogate.PrivacyWifiManager" /* PDroid */});
}
public static void checkService(ActivityBase context, String name, String[] className) {
Object service = context.getSystemService(name);
if (service == null)
sendSupportInfo("Service missing name=" + name, context);
else if (!Arrays.asList(className).contains(service.getClass().getName()))
reportClass(service.getClass(), context);
}
public static void checkCompatibility(ActivityBase context) {
for (String packageName : cIncompatible)
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
if (appInfo.enabled) {
String name = context.getPackageManager().getApplicationLabel(appInfo).toString();
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setTitle(R.string.app_name);
alertDialogBuilder.setMessage(String.format(context.getString(R.string.app_incompatible), name));
alertDialogBuilder.setIcon(context.getThemed(R.attr.icon_launcher));
alertDialogBuilder.setPositiveButton(context.getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
} catch (NameNotFoundException ex) {
}
}
private static boolean checkField(Class<?> clazz, String fieldName) {
try {
clazz.getDeclaredField(fieldName);
return true;
} catch (NoSuchFieldException ex) {
return false;
}
}
private static void reportClass(Class<?> clazz, ActivityBase context) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("Incompatible %s", clazz.getName()));
sb.append("\r\n");
sb.append("\r\n");
for (Constructor<?> constructor : clazz.getConstructors()) {
sb.append(constructor.toString());
sb.append("\r\n");
}
sb.append("\r\n");
for (Method method : clazz.getDeclaredMethods()) {
sb.append(method.toString());
sb.append("\r\n");
}
sb.append("\r\n");
for (Field field : clazz.getDeclaredFields()) {
sb.append(field.toString());
sb.append("\r\n");
}
sb.append("\r\n");
sendSupportInfo(sb.toString(), context);
}
public static void sendSupportInfo(final String text, final ActivityBase context) {
Util.log(null, Log.WARN, text);
if (Util.hasValidFingerPrint(context) && !"Genymotion".equals(Build.MANUFACTURER)) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setTitle(R.string.app_name);
alertDialogBuilder.setMessage(R.string.msg_support_info);
alertDialogBuilder.setIcon(context.getThemed(R.attr.icon_launcher));
alertDialogBuilder.setPositiveButton(context.getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int _which) {
String ourVersion = Util.getSelfVersionName(context);
StringBuilder sb = new StringBuilder(text);
sb.insert(0, "\r\n");
sb.insert(0, String.format("Id: %s\r\n", Build.ID));
sb.insert(0, String.format("Display: %s\r\n", Build.DISPLAY));
sb.insert(0, String.format("Host: %s\r\n", Build.HOST));
sb.insert(0, String.format("Device: %s\r\n", Build.DEVICE));
sb.insert(0, String.format("Product: %s\r\n", Build.PRODUCT));
sb.insert(0, String.format("Model: %s\r\n", Build.MODEL));
sb.insert(0, String.format("Manufacturer: %s\r\n", Build.MANUFACTURER));
sb.insert(0, String.format("Brand: %s\r\n", Build.BRAND));
sb.insert(0, "\r\n");
sb.insert(0, String.format("Android: %s (SDK %d)\r\n", Build.VERSION.RELEASE,
Build.VERSION.SDK_INT));
sb.insert(0, String.format("XPrivacy: %s\r\n", ourVersion));
Intent sendEmail = new Intent(Intent.ACTION_SEND);
sendEmail.setType("message/rfc822");
sendEmail.putExtra(Intent.EXTRA_EMAIL, new String[] { "marcel+support@faircode.eu" });
sendEmail.putExtra(Intent.EXTRA_SUBJECT, "XPrivacy " + ourVersion + "/"
+ Build.VERSION.RELEASE + " support info");
sendEmail.putExtra(Intent.EXTRA_TEXT, sb.toString());
try {
context.startActivity(sendEmail);
} catch (Throwable ex) {
Util.bug(null, ex);
}
}
});
alertDialogBuilder.setNegativeButton(context.getString(android.R.string.cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
}
}