Java tutorial
package biz.bokhorst.xprivacy; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.lang.StackOverflowError; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import java.lang.RuntimeException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.channels.FileChannel; import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLPeerUnverifiedException; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.HttpHostConnectException; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.Process; import android.os.RemoteException; import android.os.TransactionTooLargeException; import android.os.UserHandle; import android.util.Base64; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; @SuppressWarnings("deprecation") public class Util { private static boolean mPro = false; private static boolean mLog = true; private static boolean mLogDetermined = false; private static Boolean mHasLBE = null; private static Version MIN_PRO_VERSION = new Version("1.20"); private static String LICENSE_FILE_NAME = "XPrivacy_license.txt"; public static int NOTIFY_RESTART = 0; public static int NOTIFY_NOTXPOSED = 1; public static int NOTIFY_SERVICE = 2; public static int NOTIFY_MIGRATE = 3; public static int NOTIFY_RANDOMIZE = 4; public static int NOTIFY_UPGRADE = 5; public static int NOTIFY_UPDATE = 6; public static int NOTIFY_CORRUPT = 7; public static void log(XHook hook, int priority, String msg) { // Check if logging enabled int uid = Process.myUid(); if (!mLogDetermined && uid > 0) { mLogDetermined = true; try { mLog = PrivacyManager.getSettingBool(0, PrivacyManager.cSettingLog, false); } catch (Throwable ignored) { mLog = false; } } // Log if enabled if (priority != Log.DEBUG && (priority == Log.INFO ? mLog : true)) if (hook == null) Log.println(priority, "XPrivacy", msg); else Log.println(priority, String.format("XPrivacy/%s", hook.getClass().getSimpleName()), msg); // Report to service if (uid > 0 && priority == Log.ERROR) if (PrivacyService.isRegistered()) PrivacyService.reportErrorInternal(msg); else try { IPrivacyService client = PrivacyService.getClient(); if (client != null) client.reportError(msg); } catch (RemoteException ignored) { } } public static void bug(XHook hook, Throwable ex) { if (ex instanceof InvocationTargetException) { InvocationTargetException exex = (InvocationTargetException) ex; if (exex.getTargetException() != null) ex = exex.getTargetException(); } int priority; if (ex instanceof ActivityShare.AbortException) priority = Log.WARN; else if (ex instanceof ActivityShare.ServerException) priority = Log.WARN; else if (ex instanceof ConnectTimeoutException) priority = Log.WARN; else if (ex instanceof FileNotFoundException) priority = Log.WARN; else if (ex instanceof HttpHostConnectException) priority = Log.WARN; else if (ex instanceof NameNotFoundException) priority = Log.WARN; else if (ex instanceof NoClassDefFoundError) priority = Log.WARN; else if (ex instanceof OutOfMemoryError) priority = Log.WARN; else if (ex instanceof RuntimeException) priority = Log.WARN; else if (ex instanceof SecurityException) priority = Log.WARN; else if (ex instanceof SocketTimeoutException) priority = Log.WARN; else if (ex instanceof SSLPeerUnverifiedException) priority = Log.WARN; else if (ex instanceof StackOverflowError) priority = Log.WARN; else if (ex instanceof TransactionTooLargeException) priority = Log.WARN; else if (ex instanceof UnknownHostException) priority = Log.WARN; else if (ex instanceof UnsatisfiedLinkError) priority = Log.WARN; else priority = Log.ERROR; boolean xprivacy = false; for (StackTraceElement frame : ex.getStackTrace()) if (frame.getClassName() != null && frame.getClassName().startsWith("biz.bokhorst.xprivacy")) { xprivacy = true; break; } if (!xprivacy) priority = Log.WARN; log(hook, priority, ex.toString() + " uid=" + Process.myUid() + "\n" + Log.getStackTraceString(ex)); } public static void logStack(XHook hook, int priority) { logStack(hook, priority, false); } public static void logStack(XHook hook, int priority, boolean cl) { StringBuilder trace = new StringBuilder(); ClassLoader loader = Thread.currentThread().getContextClassLoader(); for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { trace.append(ste.toString()); if (cl) try { Class<?> clazz = Class.forName(ste.getClassName(), false, loader); trace.append(" ["); trace.append(clazz.getClassLoader().toString()); trace.append("]"); } catch (ClassNotFoundException ignored) { } trace.append("\n"); } log(hook, priority, trace.toString()); } public static boolean isXposedEnabled() { // Will be hooked to return true log(null, Log.WARN, "XPrivacy not enabled"); return false; } public static void setPro(boolean enabled) { mPro = enabled; } public static boolean isProEnabled() { return mPro; } public static String hasProLicense(Context context) { try { // Get license String[] license = getProLicenseUnchecked(); if (license == null) return null; String name = license[0]; String email = license[1]; String signature = license[2]; // Get bytes byte[] bEmail = email.getBytes("UTF-8"); byte[] bSignature = hex2bytes(signature); if (bEmail.length == 0 || bSignature.length == 0) { Util.log(null, Log.ERROR, "Licensing: invalid file"); return null; } // Verify license boolean licensed = verifyData(bEmail, bSignature, getPublicKey(context)); if (licensed) Util.log(null, Log.INFO, "Licensing: ok"); else Util.log(null, Log.ERROR, "Licensing: invalid"); // Return result if (licensed) return name; } catch (Throwable ex) { Util.bug(null, ex); } return null; } @SuppressLint("NewApi") public static int getAppId(int uid) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) try { // UserHandle: public static final int getAppId(int uid) Method method = (Method) UserHandle.class.getDeclaredMethod("getAppId", int.class); uid = (Integer) method.invoke(null, uid); } catch (Throwable ex) { Util.log(null, Log.WARN, ex.toString()); } return uid; } @SuppressLint("NewApi") public static int getUserId(int uid) { int userId = 0; if (uid > 99) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) try { // UserHandle: public static final int getUserId(int uid) Method method = (Method) UserHandle.class.getDeclaredMethod("getUserId", int.class); userId = (Integer) method.invoke(null, uid); } catch (Throwable ex) { Util.log(null, Log.WARN, ex.toString()); } } else userId = uid; return userId; } public static String getUserDataDirectory(int uid) { // Build data directory String dataDir = Environment.getDataDirectory() + File.separator; int userId = getUserId(uid); if (userId == 0) dataDir += "data"; else dataDir += "user" + File.separator + userId; dataDir += File.separator + Util.class.getPackage().getName(); return dataDir; } public static String[] getProLicenseUnchecked() { // Get license file name String storageDir = Environment.getExternalStorageDirectory().getAbsolutePath(); File licenseFile = new File(storageDir + File.separator + LICENSE_FILE_NAME); if (!licenseFile.exists()) licenseFile = new File(storageDir + File.separator + ".xprivacy" + File.separator + LICENSE_FILE_NAME); if (!licenseFile.exists()) licenseFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + File.separator + LICENSE_FILE_NAME); String importedLicense = importProLicense(licenseFile); if (importedLicense == null) return null; // Check license file licenseFile = new File(importedLicense); if (licenseFile.exists()) { // Read license try { IniFile iniFile = new IniFile(licenseFile); String name = iniFile.get("name", ""); String email = iniFile.get("email", ""); String signature = iniFile.get("signature", ""); if (name == null || email == null || signature == null) return null; else { // Check expiry if (email.endsWith("@faircode.eu")) { long expiry = Long.parseLong(email.split("\\.")[0]); long time = System.currentTimeMillis() / 1000L; if (time > expiry) { Util.log(null, Log.WARN, "Licensing: expired"); return null; } } // Valid return new String[] { name, email, signature }; } } catch (FileNotFoundException ex) { return null; } catch (Throwable ex) { bug(null, ex); return null; } } else Util.log(null, Log.INFO, "Licensing: no license file"); return null; } public static String importProLicense(File licenseFile) { // Get imported license file name String importedLicense = getUserDataDirectory(Process.myUid()) + File.separator + LICENSE_FILE_NAME; File out = new File(importedLicense); // Check if license file exists if (licenseFile.exists() && licenseFile.canRead()) { try { // Import license file Util.log(null, Log.WARN, "Licensing: importing " + out.getAbsolutePath()); InputStream is = null; is = new FileInputStream(licenseFile.getAbsolutePath()); try { OutputStream os = null; try { os = new FileOutputStream(out.getAbsolutePath()); byte[] buffer = new byte[1024]; int read; while ((read = is.read(buffer)) != -1) os.write(buffer, 0, read); os.flush(); } finally { if (os != null) os.close(); } } finally { if (is != null) is.close(); } // Protect imported license file setPermissions(out.getAbsolutePath(), 0700, Process.myUid(), Process.myUid()); // Remove original license file licenseFile.delete(); } catch (FileNotFoundException ignored) { } catch (Throwable ex) { Util.bug(null, ex); } } return (out.exists() && out.canRead() ? importedLicense : null); } public static Version getProEnablerVersion(Context context) { try { String proPackageName = context.getPackageName() + ".pro"; PackageManager pm = context.getPackageManager(); PackageInfo pi = pm.getPackageInfo(proPackageName, 0); return new Version(pi.versionName); } catch (NameNotFoundException ignored) { } catch (Throwable ex) { Util.bug(null, ex); } return null; } public static boolean isValidProEnablerVersion(Version version) { return (version.compareTo(MIN_PRO_VERSION) >= 0); } private static boolean hasValidProEnablerSignature(Context context) { return (context.getPackageManager().checkSignatures(context.getPackageName(), context.getPackageName() + ".pro") == PackageManager.SIGNATURE_MATCH); } public static boolean isProEnablerInstalled(Context context) { Version version = getProEnablerVersion(context); if (version != null && isValidProEnablerVersion(version) && hasValidProEnablerSignature(context)) { Util.log(null, Log.INFO, "Licensing: enabler installed"); return true; } Util.log(null, Log.INFO, "Licensing: enabler not installed"); return false; } public static boolean hasMarketLink(Context context, String packageName) { try { PackageManager pm = context.getPackageManager(); String installer = pm.getInstallerPackageName(packageName); if (installer != null) return installer.equals("com.android.vending") || installer.contains("google"); } catch (Exception ex) { log(null, Log.WARN, ex.toString()); } return false; } public static void viewUri(Context context, Uri uri) { Intent infoIntent = new Intent(Intent.ACTION_VIEW); infoIntent.setData(uri); if (isIntentAvailable(context, infoIntent)) context.startActivity(infoIntent); else Toast.makeText(context, "View action not available", Toast.LENGTH_LONG).show(); } public static boolean hasLBE() { if (mHasLBE == null) { mHasLBE = false; try { File apps = new File(Environment.getDataDirectory() + File.separator + "app"); File[] files = (apps == null ? null : apps.listFiles()); if (files != null) for (File file : files) if (file.getName().startsWith("com.lbe.security")) { mHasLBE = true; break; } } catch (Throwable ex) { Util.bug(null, ex); } } return mHasLBE; } public static boolean isSELinuxEnforced() { try { Class<?> cSELinux = Class.forName("android.os.SELinux"); if ((Boolean) cSELinux.getDeclaredMethod("isSELinuxEnabled").invoke(null)) if ((Boolean) cSELinux.getDeclaredMethod("isSELinuxEnforced").invoke(null)) return true; } catch (Throwable t) { } return false; } public static String getXOption(String name) { try { Class<?> cSystemProperties = Class.forName("android.os.SystemProperties"); Method spGet = cSystemProperties.getDeclaredMethod("get", String.class); String options = (String) spGet.invoke(null, "xprivacy.options"); Log.w("XPrivacy", "Options=" + options); if (options != null) for (String option : options.split(",")) { String[] nv = option.split("="); if (nv[0].equals(name)) if (nv.length > 1) return nv[1]; else return "true"; } } catch (Throwable ex) { Log.e("XPrivacy", ex.toString() + "\n" + Log.getStackTraceString(ex)); } return null; } public static int getSelfVersionCode(Context context) { try { String self = Util.class.getPackage().getName(); PackageManager pm = context.getPackageManager(); PackageInfo pInfo = pm.getPackageInfo(self, 0); return pInfo.versionCode; } catch (NameNotFoundException ex) { Util.bug(null, ex); return 0; } } public static String getSelfVersionName(Context context) { try { String self = Util.class.getPackage().getName(); PackageManager pm = context.getPackageManager(); PackageInfo pInfo = pm.getPackageInfo(self, 0); return pInfo.versionName; } catch (NameNotFoundException ex) { Util.bug(null, ex); return null; } } private static byte[] hex2bytes(String hex) { // Convert hex string to byte array int len = hex.length(); byte[] result = new byte[len / 2]; for (int i = 0; i < len; i += 2) result[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); return result; } private static PublicKey getPublicKey(Context context) throws Throwable { // Read public key String sPublicKey = ""; InputStreamReader isr = new InputStreamReader(context.getAssets().open("XPrivacy_public_key.txt"), "UTF-8"); BufferedReader br = new BufferedReader(isr); String line = br.readLine(); while (line != null) { if (!line.startsWith("-----")) sPublicKey += line; line = br.readLine(); } br.close(); isr.close(); // Create public key byte[] bPublicKey = Base64.decode(sPublicKey, Base64.NO_WRAP); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec encodedPubKeySpec = new X509EncodedKeySpec(bPublicKey); return keyFactory.generatePublic(encodedPubKeySpec); } private static boolean verifyData(byte[] data, byte[] signature, PublicKey publicKey) throws Throwable { // Verify signature Signature verifier = Signature.getInstance("SHA1withRSA"); verifier.initVerify(publicKey); verifier.update(data); return verifier.verify(signature); } public static String sha1(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { // SHA1 int userId = Util.getUserId(Process.myUid()); String salt = PrivacyManager.getSalt(userId); MessageDigest digest = MessageDigest.getInstance("SHA-1"); byte[] bytes = (text + salt).getBytes("UTF-8"); digest.update(bytes, 0, bytes.length); bytes = digest.digest(); StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02X", b)); return sb.toString(); } public static String md5(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { // MD5 int userId = Util.getUserId(Process.myUid()); String salt = PrivacyManager.getSalt(userId); byte[] bytes = MessageDigest.getInstance("MD5").digest((text + salt).getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02X", b)); return sb.toString(); } @SuppressLint("DefaultLocale") public static boolean hasValidFingerPrint(Context context) { try { PackageManager pm = context.getPackageManager(); String packageName = context.getPackageName(); PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); byte[] cert = packageInfo.signatures[0].toByteArray(); MessageDigest digest = MessageDigest.getInstance("SHA1"); byte[] bytes = digest.digest(cert); StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; ++i) sb.append((Integer.toHexString((bytes[i] & 0xFF) | 0x100)).substring(1, 3).toLowerCase()); String calculated = sb.toString(); String expected = context.getString(R.string.fingerprint); return calculated.equals(expected); } catch (Throwable ex) { bug(null, ex); return false; } } public static boolean isDebuggable(Context context) { return ((context.getApplicationContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); } public static boolean isIntentAvailable(Context context, Intent intent) { PackageManager packageManager = context.getPackageManager(); return (packageManager.queryIntentActivities(intent, PackageManager.GET_ACTIVITIES).size() > 0); } public static void setPermissions(String path, int mode, int uid, int gid) { try { // frameworks/base/core/java/android/os/FileUtils.java Class<?> fileUtils = Class.forName("android.os.FileUtils"); Method setPermissions = fileUtils.getMethod("setPermissions", String.class, int.class, int.class, int.class); setPermissions.invoke(null, path, mode, uid, gid); Util.log(null, Log.WARN, "Changed permission path=" + path + " mode=" + Integer.toOctalString(mode) + " uid=" + uid + " gid=" + gid); } catch (Throwable ex) { Util.bug(null, ex); } } public static void copy(File src, File dst) throws IOException { FileInputStream inStream = null; try { inStream = new FileInputStream(src); FileOutputStream outStream = null; try { outStream = new FileOutputStream(dst); FileChannel inChannel = inStream.getChannel(); FileChannel outChannel = outStream.getChannel(); inChannel.transferTo(0, inChannel.size(), outChannel); } finally { if (outStream != null) outStream.close(); } } finally { if (inStream != null) inStream.close(); } } public static boolean move(File src, File dst) { try { copy(src, dst); } catch (IOException ex) { Util.bug(null, ex); return false; } return src.delete(); } public static List<View> getViewsByTag(ViewGroup root, String tag) { List<View> views = new ArrayList<View>(); for (int i = 0; i < root.getChildCount(); i++) { View child = root.getChildAt(i); if (child instanceof ViewGroup) views.addAll(getViewsByTag((ViewGroup) child, tag)); if (tag.equals(child.getTag())) views.add(child); } return views; } public static float dipToPixels(Context context, float dipValue) { DisplayMetrics metrics = context.getResources().getDisplayMetrics(); return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) inSampleSize *= 2; } return inSampleSize; } public static String getSEContext() { try { Class<?> cSELinux = Class.forName("android.os.SELinux"); Method mGetContext = cSELinux.getDeclaredMethod("getContext"); return (String) mGetContext.invoke(null); } catch (Throwable ex) { Util.bug(null, ex); return null; } } }