From 1ae04c3850eb876c7f4d1956089741e747691b3a Mon Sep 17 00:00:00 2001 From: YuutaW <17158086+trumeet@users.noreply.github.com> Date: Thu, 16 May 2019 16:03:24 -0700 Subject: refactor: remove #Anti-Crack Signed-off-by: YuutaW <17158086+Trumeet@users.noreply.github.com> --- .../main/aidl/moe/yuuta/workmode/IAccessor.aidl | 4 +- .../main/java/androidx/content/pm/PackageOZ.java | 54 -- app/src/main/java/moe/yuuta/ext/ConnCallback.java | 9 - .../main/java/moe/yuuta/ext/IPCResultListener.java | 15 - .../main/java/moe/yuuta/ext/IResultListener.java | 102 ---- app/src/main/java/moe/yuuta/ext/IService.java | 102 ---- .../main/java/moe/yuuta/ext/LicServiceConn.java | 23 - .../main/java/moe/yuuta/ext/ResultCallback.java | 6 - app/src/main/java/moe/yuuta/ext/package-info.java | 4 - .../java/moe/yuuta/gplicense/AESObfuscator.java | 94 ---- .../java/moe/yuuta/gplicense/DeviceLimiter.java | 31 -- .../java/moe/yuuta/gplicense/LicenseChecker.java | 356 ------------- .../yuuta/gplicense/LicenseCheckerCallback.java | 51 -- .../java/moe/yuuta/gplicense/LicenseValidator.java | 219 -------- .../moe/yuuta/gplicense/NullDeviceLimiter.java | 16 - .../main/java/moe/yuuta/gplicense/Obfuscator.java | 32 -- app/src/main/java/moe/yuuta/gplicense/Policy.java | 49 -- .../moe/yuuta/gplicense/PreferenceObfuscator.java | 63 --- .../java/moe/yuuta/gplicense/ResponseData.java | 65 --- .../moe/yuuta/gplicense/ServerManagedPolicy.java | 282 ----------- .../moe/yuuta/gplicense/ValidationException.java | 17 - .../main/java/moe/yuuta/gplicense/util/Base64.java | 556 --------------------- .../gplicense/util/Base64DecoderException.java | 18 - .../moe/yuuta/gplicense/util/URIQueryDecoder.java | 42 -- .../main/java/moe/yuuta/workmode/MainActivity.kt | 41 +- .../java/moe/yuuta/workmode/access/AccessLayer.kt | 45 -- .../moe/yuuta/workmode/access/AccessorStarter.kt | 56 +-- .../moe/yuuta/workmode/access/WorkModeAccessor.kt | 89 +--- app/src/main/java/moe/yuuta/workmode/gpl/GPL.kt | 47 -- app/src/main/res/values/strings.xml | 11 - 30 files changed, 25 insertions(+), 2474 deletions(-) delete mode 100644 app/src/main/java/androidx/content/pm/PackageOZ.java delete mode 100644 app/src/main/java/moe/yuuta/ext/ConnCallback.java delete mode 100644 app/src/main/java/moe/yuuta/ext/IPCResultListener.java delete mode 100644 app/src/main/java/moe/yuuta/ext/IResultListener.java delete mode 100644 app/src/main/java/moe/yuuta/ext/IService.java delete mode 100644 app/src/main/java/moe/yuuta/ext/LicServiceConn.java delete mode 100644 app/src/main/java/moe/yuuta/ext/ResultCallback.java delete mode 100644 app/src/main/java/moe/yuuta/ext/package-info.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/DeviceLimiter.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/LicenseChecker.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/LicenseCheckerCallback.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/LicenseValidator.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/NullDeviceLimiter.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/Obfuscator.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/Policy.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/PreferenceObfuscator.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/ResponseData.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/ServerManagedPolicy.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/ValidationException.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/util/Base64.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/util/Base64DecoderException.java delete mode 100644 app/src/main/java/moe/yuuta/gplicense/util/URIQueryDecoder.java delete mode 100644 app/src/main/java/moe/yuuta/workmode/gpl/GPL.kt (limited to 'app/src/main') diff --git a/app/src/main/aidl/moe/yuuta/workmode/IAccessor.aidl b/app/src/main/aidl/moe/yuuta/workmode/IAccessor.aidl index 000ba9d..d6ff861 100644 --- a/app/src/main/aidl/moe/yuuta/workmode/IAccessor.aidl +++ b/app/src/main/aidl/moe/yuuta/workmode/IAccessor.aidl @@ -9,11 +9,11 @@ interface IAccessor { Bundle getSuspendedPackageAppExtras(in TransferableSuspendedApp packageInfo); Bundle getSuspendedPackageLauncherExtras(in TransferableSuspendedApp packageInfo); DumpResult dump(in TransferableSuspendedApp packageInfo); - Bundle setPackagesSuspended(in List packages, + String[] setPackagesSuspended(in List packages, boolean suspended, in PersistableBundle appExtras, in PersistableBundle launcherExtras, String dialogMessage); - Bundle apply(in Bundle dat, in TransferableSuspendedApp[] suspendList, int listMode, int status); + void apply(in TransferableSuspendedApp[] suspendList, int listMode, int status); List getInstalledApplicationsAcrossUser(int flags); } diff --git a/app/src/main/java/androidx/content/pm/PackageOZ.java b/app/src/main/java/androidx/content/pm/PackageOZ.java deleted file mode 100644 index 8a67215..0000000 --- a/app/src/main/java/androidx/content/pm/PackageOZ.java +++ /dev/null @@ -1,54 +0,0 @@ -package androidx.content.pm; - -import android.content.Context; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Calendar; - -import moe.yuuta.workmode.R; - -/** - * A wrapper of Base64 decoder. - * #Anti-Crack - */ -public class PackageOZ { - public static String decode(String source, Context context) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { - // First, get the name of Base64: java.util.Base64 - StringBuilder builder = new StringBuilder(); - - // j - builder.append("j"); - // ava - builder.append(context.getString(R.string.b6_k).toLowerCase()); - // . - builder.append("."); - // util - String litu = context.getString(R.string.b7_a); - StringBuilder reverse = new StringBuilder(); - for(int i = litu.length() - 1; i >= 0; i--) { - reverse.append(litu.charAt(i)); - } - builder.append(reverse.toString()); - // . - builder.append('.'); - // Base - String base = context.getString(R.string.b90_key); - base = base.substring(14, 18); - base = base.substring(1, 4); - builder.append("B"); - builder.append(base); - // 64 - Calendar calendar = Calendar.getInstance(); - builder.append((int) Math.pow(1, calendar.getWeekYear()) * 128 / 2); - // Then find the class and methods - Class b6 = Class.forName(builder.toString()); - Method method = b6.getDeclaredMethod("get" + context.getString(R.string.b99_a) + - context.getString(R.string.b100_a)); - Object dec = method.invoke(null); - Method de = dec.getClass().getDeclaredMethod(dec.getClass().getSimpleName().toLowerCase().replace("r", ""), - String.class); - // Finally decode - return new String((byte[])de.invoke(dec, source)); - } -} diff --git a/app/src/main/java/moe/yuuta/ext/ConnCallback.java b/app/src/main/java/moe/yuuta/ext/ConnCallback.java deleted file mode 100644 index 13af66d..0000000 --- a/app/src/main/java/moe/yuuta/ext/ConnCallback.java +++ /dev/null @@ -1,9 +0,0 @@ -package moe.yuuta.ext; - -import android.content.ComponentName; -import android.os.IBinder; - -public interface ConnCallback { - void onServiceConnected(ComponentName name, IBinder service); - void onServiceDisconnected(ComponentName name); -} diff --git a/app/src/main/java/moe/yuuta/ext/IPCResultListener.java b/app/src/main/java/moe/yuuta/ext/IPCResultListener.java deleted file mode 100644 index bf0045b..0000000 --- a/app/src/main/java/moe/yuuta/ext/IPCResultListener.java +++ /dev/null @@ -1,15 +0,0 @@ -package moe.yuuta.ext; - -public class IPCResultListener extends IResultListener.Stub { - private final ResultCallback mCallback; - - public IPCResultListener(ResultCallback mCallback) { - this.mCallback = mCallback; - } - - @Override - public void exec(final int responseCode, final String signedData, - final String signature) { - mCallback.verifyLicense(responseCode, signedData, signature); - } -} diff --git a/app/src/main/java/moe/yuuta/ext/IResultListener.java b/app/src/main/java/moe/yuuta/ext/IResultListener.java deleted file mode 100644 index 706b34a..0000000 --- a/app/src/main/java/moe/yuuta/ext/IResultListener.java +++ /dev/null @@ -1,102 +0,0 @@ -package moe.yuuta.ext; - -import android.os.Binder; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Parcel; -import android.os.RemoteException; - -import androidx.annotation.NonNull; - -import moe.yuuta.gplicense.util.Base64; -import moe.yuuta.gplicense.util.Base64DecoderException; - -public interface IResultListener extends IInterface { - abstract class Stub extends Binder implements IResultListener { - private static final String DESCRIPTOR; - - static { - try { - // Base64 encoded - - // com.android.vending.licensing.ILicenseResultListener - DESCRIPTOR = new String(Base64.decode( - "Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2VSZXN1bHRMaXN0ZW5lcg==")); - } catch (Base64DecoderException e) { - throw new RuntimeException(e); - } - } - - protected Stub() { - this.attachInterface(this, DESCRIPTOR); - } - - static IResultListener asInterface(IBinder obj) { - if ((obj == null)) { - return null; - } - IInterface iin = obj.queryLocalInterface(DESCRIPTOR); - if (iin instanceof IResultListener) { - return (IResultListener) iin; - } - return new Stub.Proxy(obj); - } - - @Override - public IBinder asBinder() { - return this; - } - - @Override - public boolean onTransact(int code, @NonNull Parcel data, Parcel reply, int flags) throws RemoteException { - switch (code) { - case INTERFACE_TRANSACTION: { - reply.writeString(DESCRIPTOR); - return true; - } - case TRANSACTION: { - data.enforceInterface(DESCRIPTOR); - int responseCode = data.readInt(); - String signedData = data.readString(); - String signature = data.readString(); - this.exec(responseCode, signedData, signature); - return true; - } - default: { - return super.onTransact(code, data, reply, flags); - } - } - } - - private static class Proxy implements IResultListener { - private IBinder mRemote; - - Proxy(IBinder remote) { - mRemote = remote; - } - - @Override - public IBinder asBinder() { - return mRemote; - } - - @Override - public void exec(int responseCode, String signedData, String signature) throws RemoteException { - Parcel args = Parcel.obtain(); - try { - args.writeInterfaceToken(DESCRIPTOR); - args.writeInt(responseCode); - args.writeString(signedData); - args.writeString(signature); - mRemote.transact(TRANSACTION, args, null, IBinder.FLAG_ONEWAY); - } finally { - args.recycle(); - } - } - } - - static final int TRANSACTION = IBinder.FIRST_CALL_TRANSACTION; - } - - // verifyLicense - void exec(int responseCode, String signedData, String signature) throws RemoteException; -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/ext/IService.java b/app/src/main/java/moe/yuuta/ext/IService.java deleted file mode 100644 index ea0b136..0000000 --- a/app/src/main/java/moe/yuuta/ext/IService.java +++ /dev/null @@ -1,102 +0,0 @@ -package moe.yuuta.ext; - -import android.os.Binder; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Parcel; -import android.os.RemoteException; - -import androidx.annotation.NonNull; - -import moe.yuuta.gplicense.util.Base64; -import moe.yuuta.gplicense.util.Base64DecoderException; - -public interface IService extends IInterface { - abstract class Stub extends Binder implements IService { - private static final String DESCRIPTOR; - - static { - try { - // Base64 encoded - - // com.android.vending.licensing.ILicensingService - DESCRIPTOR = new String(Base64.decode( - "Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")); - } catch (Base64DecoderException e) { - throw new RuntimeException(e); - } - } - - public Stub() { - this.attachInterface(this, DESCRIPTOR); - } - - public static IService asInterface(IBinder obj) { - if ((obj == null)) { - return null; - } - IInterface iin = obj.queryLocalInterface(DESCRIPTOR); - if (iin instanceof IService) { - return (IService) iin; - } - return new IService.Stub.Proxy(obj); - } - - @Override - public IBinder asBinder() { - return this; - } - - @Override - public boolean onTransact(int code, @NonNull Parcel data, Parcel reply, int flags) throws RemoteException { - switch (code) { - case INTERFACE_TRANSACTION: { - reply.writeString(DESCRIPTOR); - return true; - } - case TRANSACTION_checkLicense: { - data.enforceInterface(DESCRIPTOR); - long nonce = data.readLong(); - String packageName = data.readString(); - IResultListener listener = IResultListener.Stub.asInterface(data.readStrongBinder()); - this.exec(nonce, packageName, listener); - return true; - } - default: { - return super.onTransact(code, data, reply, flags); - } - } - } - - private static class Proxy implements IService { - private IBinder mRemote; - - Proxy(IBinder remote) { - mRemote = remote; - } - - @Override - public IBinder asBinder() { - return mRemote; - } - - @Override - public void exec(long nonce, String packageName, IResultListener listener) throws RemoteException { - Parcel args = Parcel.obtain(); - try { - args.writeInterfaceToken(DESCRIPTOR); - args.writeLong(nonce); - args.writeString(packageName); - args.writeStrongBinder(listener != null ? listener.asBinder() : null); - mRemote.transact(TRANSACTION_checkLicense, args, null, IBinder.FLAG_ONEWAY); - } finally { - args.recycle(); - } - } - } - - static final int TRANSACTION_checkLicense = IBinder.FIRST_CALL_TRANSACTION; - } - - // checkLicense - void exec(long nonce, String packageName, IResultListener listener) throws RemoteException; -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/ext/LicServiceConn.java b/app/src/main/java/moe/yuuta/ext/LicServiceConn.java deleted file mode 100644 index f0ad766..0000000 --- a/app/src/main/java/moe/yuuta/ext/LicServiceConn.java +++ /dev/null @@ -1,23 +0,0 @@ -package moe.yuuta.ext; - -import android.content.ComponentName; -import android.content.ServiceConnection; -import android.os.IBinder; - -public class LicServiceConn implements ServiceConnection { - private final ConnCallback mCallback; - - public LicServiceConn(ConnCallback mCallback) { - this.mCallback = mCallback; - } - - @Override - public synchronized void onServiceConnected(ComponentName name, IBinder service) { - mCallback.onServiceConnected(name, service); - } - - @Override - public synchronized void onServiceDisconnected(ComponentName name) { - mCallback.onServiceDisconnected(name); - } -} diff --git a/app/src/main/java/moe/yuuta/ext/ResultCallback.java b/app/src/main/java/moe/yuuta/ext/ResultCallback.java deleted file mode 100644 index 4feafba..0000000 --- a/app/src/main/java/moe/yuuta/ext/ResultCallback.java +++ /dev/null @@ -1,6 +0,0 @@ -package moe.yuuta.ext; - -public interface ResultCallback { - void verifyLicense(final int responseCode, final String signedData, - final String signature); -} diff --git a/app/src/main/java/moe/yuuta/ext/package-info.java b/app/src/main/java/moe/yuuta/ext/package-info.java deleted file mode 100644 index 128e042..0000000 --- a/app/src/main/java/moe/yuuta/ext/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Classes which cannot be processed by ProGuard are moved to here to keep LicenseChecker processed. - */ -package moe.yuuta.ext; \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java b/app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java deleted file mode 100644 index 7370f58..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java +++ /dev/null @@ -1,94 +0,0 @@ -package moe.yuuta.gplicense; - -import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; -import java.security.spec.KeySpec; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -import moe.yuuta.gplicense.util.Base64; -import moe.yuuta.gplicense.util.Base64DecoderException; - -/** - * An Obfuscator that uses AES to encrypt data. - */ -public class AESObfuscator implements Obfuscator { - private static final String UTF8 = "UTF-8"; - private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC"; - private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; - private static final byte[] IV = - {16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74}; - private static final String header = "moe.yuuta.workmode.AO-1|"; - - private Cipher mEncryptor; - private Cipher mDecryptor; - - /** - * @param salt an array of random bytes to use for each (un)obfuscation - * @param applicationId application identifier, e.g. the package name - * @param deviceId device identifier. Use as many sources as possible to - * create this unique identifier. - */ - public AESObfuscator(byte[] salt, String applicationId, String deviceId) { - try { - SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM); - KeySpec keySpec = - new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256); - SecretKey tmp = factory.generateSecret(keySpec); - SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); - mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM); - mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV)); - mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM); - mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV)); - } catch (GeneralSecurityException e) { - // This can't happen on a compatible Android device. - throw new RuntimeException("Invalid environment", e); - } - } - - public String obfuscate(String original, String key) { - if (original == null) { - return null; - } - try { - // Header is appended as an integrity check - return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8))); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Invalid environment", e); - } catch (GeneralSecurityException e) { - throw new RuntimeException("Invalid environment", e); - } - } - - public String unobfuscate(String obfuscated, String key) throws ValidationException { - if (obfuscated == null) { - return null; - } - try { - String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8); - // Check for presence of header. This serves as a final integrity check, for cases - // where the block size is correct during decryption. - int headerIndex = result.indexOf(header + key); - if (headerIndex != 0) { - throw new ValidationException("Header not found (invalid data or key)" + ":" + - obfuscated); - } - return result.substring(header.length() + key.length(), result.length()); - } catch (Base64DecoderException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (IllegalBlockSizeException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (BadPaddingException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Invalid environment", e); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/DeviceLimiter.java b/app/src/main/java/moe/yuuta/gplicense/DeviceLimiter.java deleted file mode 100644 index c2343d9..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/DeviceLimiter.java +++ /dev/null @@ -1,31 +0,0 @@ -package moe.yuuta.gplicense; - -/** - * Allows the developer to limit the number of devices using a single license. - *

- * The LICENSED response from the server contains a user identifier unique to - * the <application, user> pair. The developer can send this identifier - * to their own server along with some device identifier (a random number - * generated and stored once per application installation, - * {@link android.telephony.TelephonyManager#getDeviceId getDeviceId}, - * {@link android.provider.Settings.Secure#ANDROID_ID ANDROID_ID}, etc). - * The more sources used to identify the device, the harder it will be for an - * attacker to spoof. - *

- * The server can look at the <application, user, device id> tuple and - * restrict a user's application license to run on at most 10 different devices - * in a week (for example). We recommend not being too restrictive because a - * user might legitimately have multiple devices or be in the process of - * changing phones. This will catch egregious violations of multiple people - * sharing one license. - */ -public interface DeviceLimiter { - - /** - * Checks if this device is allowed to use the given user's license. - * - * @param userId the user whose license the server responded with - * @return LICENSED if the device is allowed, NOT_LICENSED if not, RETRY if an error occurs - */ - int isDeviceAllowed(String userId); -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/LicenseChecker.java b/app/src/main/java/moe/yuuta/gplicense/LicenseChecker.java deleted file mode 100644 index e909aec..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/LicenseChecker.java +++ /dev/null @@ -1,356 +0,0 @@ -package moe.yuuta.gplicense; - -import android.annotation.SuppressLint; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.RemoteException; -import android.provider.Settings.Secure; -import com.elvishew.xlog.Logger; -import com.elvishew.xlog.XLog; -import moe.yuuta.ext.*; -import moe.yuuta.gplicense.util.Base64; -import moe.yuuta.gplicense.util.Base64DecoderException; -import moe.yuuta.workmode.BuildConfig; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -import java.util.*; - -/** - * Client library for Google Play license verifications. - *

- * The LicenseChecker is configured via a {@link Policy} which contains the logic to determine - * whether a user should have access to the application. For example, the Policy can define a - * threshold for allowable number of server or client failures before the library reports the user - * as not having access. - *

- * Must also provide the Base64-encoded RSA public key associated with your developer account. The - * public key is obtainable from the publisher site. - */ -public class LicenseChecker implements ConnCallback { - private static final Logger logger = XLog.tag("LCK").build(); - - private static final String KEY_FACTORY_ALGORITHM = "RSA"; - - // Timeout value (in milliseconds) for calls to service. - private static final int TIMEOUT_MS = 10 * 1000; - - private static final SecureRandom RANDOM = new SecureRandom(); - private static final boolean DEBUG_LICENSE_ERROR = BuildConfig.DEBUG; - - private IService mService; - - private PublicKey mPublicKey; - private final Context mContext; - private final Policy mPolicy; - /** - * A handler for running tasks on a background thread. We don't want license processing to block - * the UI thread. - */ - private Handler mHandler; - private final String mPackageName; - private final String mVersionCode; - private final Set mChecksInProgress = new HashSet(); - private final Queue mPendingChecks = new LinkedList(); - - private LicServiceConn mConn; - - /** - * @param context a Context - * @param policy implementation of Policy - * @param encodedPublicKey Base64-encoded RSA public key - * @throws IllegalArgumentException if encodedPublicKey is invalid - */ - public LicenseChecker(Context context, Policy policy, String encodedPublicKey) { - mContext = context; - mPolicy = policy; - mPublicKey = generatePublicKey(encodedPublicKey); - mPackageName = mContext.getPackageName(); - mVersionCode = getVersionCode(context, mPackageName); - HandlerThread handlerThread = new HandlerThread("background thread"); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); - } - - /** - * Generates a PublicKey instance from a string containing the Base64-encoded public key. - * - * @param encodedPublicKey Base64-encoded public key - * @throws IllegalArgumentException if encodedPublicKey is invalid - */ - private static PublicKey generatePublicKey(String encodedPublicKey) { - try { - byte[] decodedKey = Base64.decode(encodedPublicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); - - return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); - } catch (NoSuchAlgorithmException e) { - // This won't happen in an Android-compatible environment. - throw new RuntimeException(e); - } catch (Base64DecoderException e) { - logger.e("Could not decode from Base64."); - throw new IllegalArgumentException(e); - } catch (InvalidKeySpecException e) { - logger.e("Invalid key specification."); - throw new IllegalArgumentException(e); - } - } - - /** - * Checks if the user should have access to the app. Binds the service if necessary. - *

- * NOTE: This call uses a trivially obfuscated string (base64-encoded). For best security, we - * recommend obfuscating the string that is passed into bindService using another method of your - * own devising. - *

- * source string: "com.android.vending.licensing.IService" - *

- * - * @param callback - */ - public synchronized void checkAccess(LicenseCheckerCallback callback) { - // If we have a valid recent LICENSED response, we can skip asking - // Market. - if (mPolicy.allowAccess()) { - logger.i("Using cached response"); - callback.allow(Policy.LICENSED); - } else { - LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(), - callback, generateNonce(), mPackageName, mVersionCode); - - if (mService == null) { - logger.i("Binding to service."); - try { - mConn = new LicServiceConn(this); - boolean bindResult = mContext - .bindService( - new Intent( - new String( - // Base64 encoded - - // com.android.vending.licensing.IService - // Consider encoding this in another way in your - // code to improve security - Base64.decode( - "Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))) - // As of Android 5.0, implicit - // Service Intents are no longer - // allowed because it's not - // possible for the user to - // participate in disambiguating - // them. This does mean we break - // compatibility with Android - // Cupcake devices with this - // release, since setPackage was - // added in Donut. - .setPackage( - new String( - // Base64 - // encoded - - // com.android.vending - Base64.decode( - "Y29tLmFuZHJvaWQudmVuZGluZw=="))), - mConn, // ServiceConnection. - Context.BIND_AUTO_CREATE); - if (bindResult) { - mPendingChecks.offer(validator); - } else { - logger.e("Could not bind to service."); - handleServiceConnectionError(validator); - } - } catch (SecurityException e) { - callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION); - } catch (Base64DecoderException e) { - e.printStackTrace(); - } - } else { - mPendingChecks.offer(validator); - runChecks(); - } - } - } - - private void runChecks() { - LicenseValidator validator; - while ((validator = mPendingChecks.poll()) != null) { - try { - logger.i("Executing on service for " + validator.getPackageName()); - mService.exec( - validator.getNonce(), validator.getPackageName(), - new IPCResultListener(new ResultListener(validator))); - mChecksInProgress.add(validator); - } catch (RemoteException e) { - logger.w("RemoteException in exec call.", e); - handleServiceConnectionError(validator); - } - } - } - - private synchronized void finishCheck(LicenseValidator validator) { - mChecksInProgress.remove(validator); - if (mChecksInProgress.isEmpty()) { - cleanupService(); - } - } - - private class ResultListener implements ResultCallback { - private final LicenseValidator mValidator; - private Runnable mOnTimeout; - - public ResultListener(LicenseValidator validator) { - mValidator = validator; - mOnTimeout = () -> { - logger.i("Check timed out."); - handleServiceConnectionError(mValidator); - finishCheck(mValidator); - }; - startTimeout(); - } - - private static final int ERROR_CONTACTING_SERVER = 0x101; - private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; - private static final int ERROR_NON_MATCHING_UID = 0x103; - - // Runs in IPC thread pool. Post it to the Handler, so we can guarantee - // either this or the timeout runs. - @Override - public void verifyLicense(final int responseCode, final String signedData, - final String signature) { - mHandler.post(() -> { - logger.i("Received response. (code: " + responseCode + ")"); - // Make sure it hasn't already timed out. - if (mChecksInProgress.contains(mValidator)) { - clearTimeout(); - mValidator.verify(mPublicKey, responseCode, signedData, signature); - finishCheck(mValidator); - } - if (DEBUG_LICENSE_ERROR) { - boolean logResponse; - String stringError = null; - switch (responseCode) { - case ERROR_CONTACTING_SERVER: - logResponse = true; - stringError = "ERROR_CONTACTING_SERVER"; - break; - case ERROR_INVALID_PACKAGE_NAME: - logResponse = true; - stringError = "ERROR_INVALID_PACKAGE_NAME"; - break; - case ERROR_NON_MATCHING_UID: - logResponse = true; - stringError = "ERROR_NON_MATCHING_UID"; - break; - default: - logResponse = false; - } - - if (logResponse) { - @SuppressLint("HardwareIds") String android_id = Secure.getString(mContext.getContentResolver(), - Secure.ANDROID_ID); - Date date = new Date(); - logger.d("Server Failure: " + stringError); - logger.d("Android ID: " + android_id); - logger.d("Time: " + date.toGMTString()); - } - } - - }); - } - - private void startTimeout() { - logger.i("Start monitoring timeout."); - mHandler.postDelayed(mOnTimeout, TIMEOUT_MS); - } - - private void clearTimeout() { - logger.i("Clearing timeout."); - mHandler.removeCallbacks(mOnTimeout); - } - } - - @Override - public synchronized void onServiceConnected(ComponentName name, IBinder service) { - mService = IService.Stub.asInterface(service); - runChecks(); - } - - @Override - public synchronized void onServiceDisconnected(ComponentName name) { - // Called when the connection with the service has been - // unexpectedly disconnected. That is, Market crashed. - // If there are any checks in progress, the timeouts will handle them. - logger.w("Service unexpectedly disconnected."); - mService = null; - } - - /** - * Generates policy response for service connection errors, as a result of disconnections or - * timeouts. - */ - private synchronized void handleServiceConnectionError(LicenseValidator validator) { - mPolicy.processServerResponse(Policy.RETRY, null); - - if (mPolicy.allowAccess()) { - validator.getCallback().allow(Policy.RETRY); - } else { - validator.getCallback().dontAllow(Policy.RETRY); - } - } - - /** Unbinds service if necessary and removes reference to it. */ - private void cleanupService() { - if (mService != null) { - try { - mContext.unbindService(mConn); - } catch (IllegalArgumentException e) { - // Somehow we've already been unbound. This is a non-fatal - // error. - logger.e("Unable to unbind from licensing service (already unbound)"); - } - mService = null; - } - } - - /** - * Inform the library that the hostContext is about to be destroyed, so that any open connections - * can be cleaned up. - *

- * Failure to call this method can result in a crash under certain circumstances, such as during - * screen rotation if an Activity requests the license check or when the user exits the - * application. - */ - public synchronized void onDestroy() { - cleanupService(); - mHandler.getLooper().quit(); - } - - /** Generates a nonce (number used once). */ - private int generateNonce() { - return RANDOM.nextInt(); - } - - /** - * Get version code for the application package name. - * - * @param context - * @param packageName application package name - * @return the version code or empty string if package not found - */ - private static String getVersionCode(Context context, String packageName) { - try { - return String.valueOf( - context.getPackageManager().getPackageInfo(packageName, 0).versionCode); - } catch (NameNotFoundException e) { - logger.e("Package not found. could not get version code."); - return ""; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/LicenseCheckerCallback.java b/app/src/main/java/moe/yuuta/gplicense/LicenseCheckerCallback.java deleted file mode 100644 index 214f152..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/LicenseCheckerCallback.java +++ /dev/null @@ -1,51 +0,0 @@ -package moe.yuuta.gplicense; - -/** - * Callback for the license checker library. - *

- * Upon checking with the Market server and conferring with the {@link Policy}, - * the library calls the appropriate callback method to communicate the result. - *

- * The callback does not occur in the original checking thread. Your - * application should post to the appropriate handling thread or lock - * accordingly. - *

- * The reason that is passed back with allow/dontAllow is the base status handed - * to the policy for allowed/disallowing the license. Policy.RETRY will call - * allow or dontAllow depending on other statistics associated with the policy, - * while in most cases Policy.NOT_LICENSED will call dontAllow and - * Policy.LICENSED will Allow. - */ -public interface LicenseCheckerCallback { - - /** - * Allow use. App should proceed as normal. - * - * @param reason Policy.LICENSED or Policy.RETRY typically. (although in - * theory the policy can return Policy.NOT_LICENSED here as well) - */ - void allow(int reason); - - /** - * Don't allow use. App should inform user and take appropriate action. - * - * @param reason Policy.NOT_LICENSED or Policy.RETRY. (although in theory - * the policy can return Policy.LICENSED here as well --- - * perhaps the call to the LVL took too long, for example) - */ - void dontAllow(int reason); - - /** Application error codes. */ - int ERROR_INVALID_PACKAGE_NAME = 1; - int ERROR_NON_MATCHING_UID = 2; - int ERROR_NOT_MARKET_MANAGED = 3; - int ERROR_CHECK_IN_PROGRESS = 4; - int ERROR_INVALID_PUBLIC_KEY = 5; - int ERROR_MISSING_PERMISSION = 6; - - /** - * Error in application code. Caller did not call or set up license checker - * correctly. Should be considered fatal. - */ - void applicationError(int errorCode); -} diff --git a/app/src/main/java/moe/yuuta/gplicense/LicenseValidator.java b/app/src/main/java/moe/yuuta/gplicense/LicenseValidator.java deleted file mode 100644 index d34d614..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/LicenseValidator.java +++ /dev/null @@ -1,219 +0,0 @@ -package moe.yuuta.gplicense; - -import android.text.TextUtils; - -import com.elvishew.xlog.Logger; -import com.elvishew.xlog.XLog; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; - -import moe.yuuta.gplicense.util.Base64; -import moe.yuuta.gplicense.util.Base64DecoderException; - -/** - * Contains data related to a licensing request and methods to verify - * and process the response. - */ -class LicenseValidator { - private final Logger logger = XLog.tag("LVter").build(); - - // Server response codes. - private static final int LICENSED = 0x0; - private static final int NOT_LICENSED = 0x1; - private static final int LICENSED_OLD_KEY = 0x2; - private static final int ERROR_NOT_MARKET_MANAGED = 0x3; - private static final int ERROR_SERVER_FAILURE = 0x4; - private static final int ERROR_OVER_QUOTA = 0x5; - - private static final int ERROR_CONTACTING_SERVER = 0x101; - private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; - private static final int ERROR_NON_MATCHING_UID = 0x103; - - private final Policy mPolicy; - private final LicenseCheckerCallback mCallback; - private final int mNonce; - private final String mPackageName; - private final String mVersionCode; - private final DeviceLimiter mDeviceLimiter; - - LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback, - int nonce, String packageName, String versionCode) { - mPolicy = policy; - mDeviceLimiter = deviceLimiter; - mCallback = callback; - mNonce = nonce; - mPackageName = packageName; - mVersionCode = versionCode; - } - - public LicenseCheckerCallback getCallback() { - return mCallback; - } - - public int getNonce() { - return mNonce; - } - - public String getPackageName() { - return mPackageName; - } - - private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; - - /** - * Verifies the response from server and calls appropriate callback method. - * - * @param publicKey public key associated with the developer account - * @param responseCode server response code - * @param signedData signed data from server - * @param signature server signature - */ - public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) { - String userId = null; - // Skip signature check for unsuccessful requests - ResponseData data = null; - if (responseCode == LICENSED || responseCode == NOT_LICENSED || - responseCode == LICENSED_OLD_KEY) { - // Verify signature. - try { - if (TextUtils.isEmpty(signedData)) { - logger.e("Signature verification failed: signedData is empty. " + - "(Device not signed-in to any Google accounts?)"); - handleInvalidResponse(); - return; - } - - Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); - sig.initVerify(publicKey); - sig.update(signedData.getBytes()); - - if (!sig.verify(Base64.decode(signature))) { - logger.e("Signature verification failed."); - handleInvalidResponse(); - return; - } - } catch (NoSuchAlgorithmException e) { - // This can't happen on an Android compatible device. - throw new RuntimeException(e); - } catch (InvalidKeyException e) { - handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY); - return; - } catch (SignatureException e) { - throw new RuntimeException(e); - } catch (Base64DecoderException e) { - logger.e("Could not Base64-decode signature."); - handleInvalidResponse(); - return; - } - - // Parse and validate response. - try { - data = ResponseData.parse(signedData); - } catch (IllegalArgumentException e) { - logger.e("Could not parse response."); - handleInvalidResponse(); - return; - } - - if (data.responseCode != responseCode) { - logger.e("Response codes don't match."); - handleInvalidResponse(); - return; - } - - if (data.nonce != mNonce) { - logger.e("Nonce doesn't match."); - handleInvalidResponse(); - return; - } - - if (!data.packageName.equals(mPackageName)) { - logger.e("Package name doesn't match."); - handleInvalidResponse(); - return; - } - - if (!data.versionCode.equals(mVersionCode)) { - logger.e("Version codes don't match."); - handleInvalidResponse(); - return; - } - - // Application-specific user identifier. - userId = data.userId; - if (TextUtils.isEmpty(userId)) { - logger.e("User identifier is empty."); - handleInvalidResponse(); - return; - } - } - - logger.d("Final code: " + responseCode); - switch (responseCode) { - case LICENSED: - case LICENSED_OLD_KEY: - int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId); - handleResponse(limiterResponse, data); - break; - case NOT_LICENSED: - handleResponse(Policy.NOT_LICENSED, data); - break; - case ERROR_CONTACTING_SERVER: - logger.w("Error contacting licensing server."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_SERVER_FAILURE: - logger.w("An error has occurred on the licensing server."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_OVER_QUOTA: - logger.w("Licensing server is refusing to talk to this device, over quota."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_INVALID_PACKAGE_NAME: - handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME); - break; - case ERROR_NON_MATCHING_UID: - handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID); - break; - case ERROR_NOT_MARKET_MANAGED: - handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED); - break; - default: - logger.e("Unknown response code for checking."); - handleInvalidResponse(); - } - } - - /** - * Confers with policy and calls appropriate callback method. - * - * @param response - * @param rawData - */ - private void handleResponse(int response, ResponseData rawData) { - logger.d("handle: " + response); - // Update policy data and increment retry counter (if needed) - mPolicy.processServerResponse(response, rawData); - - // Given everything we know, including cached data, ask the policy if we should grant - // access. - if (mPolicy.allowAccess()) { - mCallback.allow(response); - } else { - mCallback.dontAllow(response); - } - } - - private void handleApplicationError(int code) { - mCallback.applicationError(code); - } - - private void handleInvalidResponse() { - mCallback.dontAllow(Policy.NOT_LICENSED); - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/NullDeviceLimiter.java b/app/src/main/java/moe/yuuta/gplicense/NullDeviceLimiter.java deleted file mode 100644 index 63e7ae3..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/NullDeviceLimiter.java +++ /dev/null @@ -1,16 +0,0 @@ -package moe.yuuta.gplicense; - -/** - * A DeviceLimiter that doesn't limit the number of devices that can use a - * given user's license. - *

- * Unless you have reason to believe that your application is being pirated - * by multiple users using the same license (signing in to Market as the same - * user), we recommend you use this implementation. - */ -public class NullDeviceLimiter implements DeviceLimiter { - - public int isDeviceAllowed(String userId) { - return Policy.LICENSED; - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/Obfuscator.java b/app/src/main/java/moe/yuuta/gplicense/Obfuscator.java deleted file mode 100644 index e89030c..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/Obfuscator.java +++ /dev/null @@ -1,32 +0,0 @@ -package moe.yuuta.gplicense; - -/** - * Interface used as part of a {@link Policy} to allow application authors to obfuscate - * licensing data that will be stored into a SharedPreferences file. - *

- * Any transformation scheme must be reversable. Implementing classes may optionally implement an - * integrity check to further prevent modification to preference data. Implementing classes - * should use device-specific information as a key in the obfuscation algorithm to prevent - * obfuscated preferences from being shared among devices. - */ -public interface Obfuscator { - - /** - * Obfuscate a string that is being stored into shared preferences. - * - * @param original The data that is to be obfuscated. - * @param key The key for the data that is to be obfuscated. - * @return A transformed version of the original data. - */ - String obfuscate(String original, String key); - - /** - * Undo the transformation applied to data by the obfuscate() method. - * - * @param obfuscated The data that is to be un-obfuscated. - * @param key The key for the data that is to be un-obfuscated. - * @return The original data transformed by the obfuscate() method. - * @throws ValidationException Optionally thrown if a data integrity check fails. - */ - String unobfuscate(String obfuscated, String key) throws ValidationException; -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/Policy.java b/app/src/main/java/moe/yuuta/gplicense/Policy.java deleted file mode 100644 index c261d82..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/Policy.java +++ /dev/null @@ -1,49 +0,0 @@ -package moe.yuuta.gplicense; - -/** - * Policy used by {@link LicenseChecker} to determine whether a user should have - * access to the application. - */ -public interface Policy { - - /** - * Change these values to make it more difficult for tools to automatically - * strip LVL protection from your APK. - */ - - /** - * LICENSED means that the server returned back a valid license response - */ - int LICENSED = 0x0100; - /** - * NOT_LICENSED means that the server returned back a valid license response - * that indicated that the user definitively is not licensed - */ - int NOT_LICENSED = 0x0231; - /** - * RETRY means that the license response was unable to be determined --- - * perhaps as a result of faulty networking - */ - int RETRY = 0x0123; - - /** - * Provide results from contact with the license server. Retry counts are - * incremented if the current value of response is RETRY. Results will be - * used for any future policy decisions. - * - * @param response the result from validating the server response - * @param rawData the raw server response data, can be null for RETRY - */ - void processServerResponse(int response, ResponseData rawData); - - /** - * Check if the user should be allowed access to the application. - */ - boolean allowAccess(); - - /** - * Gets the licensing URL returned by the server that can enable access for unlicensed apps (e.g. - * buy app on the Play Store). - */ - String getLicensingUrl(); -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/PreferenceObfuscator.java b/app/src/main/java/moe/yuuta/gplicense/PreferenceObfuscator.java deleted file mode 100644 index 0b02276..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/PreferenceObfuscator.java +++ /dev/null @@ -1,63 +0,0 @@ -package moe.yuuta.gplicense; - -import android.content.SharedPreferences; - -import com.elvishew.xlog.Logger; -import com.elvishew.xlog.XLog; - -/** - * An wrapper for SharedPreferences that transparently performs data obfuscation. - */ -public class PreferenceObfuscator { - - private static final Logger logger = XLog.tag("PrefObfs").build(); - - private final SharedPreferences mPreferences; - private final Obfuscator mObfuscator; - private SharedPreferences.Editor mEditor; - - /** - * Constructor. - * - * @param sp A SharedPreferences instance provided by the system. - * @param o The Obfuscator to use when reading or writing data. - */ - public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) { - mPreferences = sp; - mObfuscator = o; - mEditor = null; - } - - public void putString(String key, String value) { - if (mEditor == null) { - mEditor = mPreferences.edit(); - } - String obfuscatedValue = mObfuscator.obfuscate(value, key); - mEditor.putString(key, obfuscatedValue); - } - - public String getString(String key, String defValue) { - String result; - String value = mPreferences.getString(key, null); - if (value != null) { - try { - result = mObfuscator.unobfuscate(value, key); - } catch (ValidationException e) { - // Unable to unobfuscate, data corrupt or tampered - logger.w("Validation error while reading preference: " + key); - result = defValue; - } - } else { - // Preference not found - result = defValue; - } - return result; - } - - public void commit() { - if (mEditor != null) { - mEditor.commit(); - mEditor = null; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/ResponseData.java b/app/src/main/java/moe/yuuta/gplicense/ResponseData.java deleted file mode 100644 index e588644..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/ResponseData.java +++ /dev/null @@ -1,65 +0,0 @@ -package moe.yuuta.gplicense; - -import android.text.TextUtils; - -import java.util.regex.Pattern; - -/** - * ResponseData from licensing server. - */ -public class ResponseData { - - public int responseCode; - public int nonce; - public String packageName; - public String versionCode; - public String userId; - public long timestamp; - /** Response-specific data. */ - public String extra; - - /** - * Parses response string into ResponseData. - * - * @param responseData response data string - * @throws IllegalArgumentException upon parsing error - * @return ResponseData object - */ - public static ResponseData parse(String responseData) { - // Must parse out main response data and response-specific data. - int index = responseData.indexOf(':'); - String mainData, extraData; - if (-1 == index) { - mainData = responseData; - extraData = ""; - } else { - mainData = responseData.substring(0, index); - extraData = index >= responseData.length() ? "" : responseData.substring(index + 1); - } - - String[] fields = TextUtils.split(mainData, Pattern.quote("|")); - if (fields.length < 6) { - throw new IllegalArgumentException("Wrong number of fields."); - } - - ResponseData data = new ResponseData(); - data.extra = extraData; - data.responseCode = Integer.parseInt(fields[0]); - data.nonce = Integer.parseInt(fields[1]); - data.packageName = fields[2]; - data.versionCode = fields[3]; - // Application-specific user identifier. - data.userId = fields[4]; - data.timestamp = Long.parseLong(fields[5]); - - return data; - } - - @Override - public String toString() { - return TextUtils.join("|", new Object[] { - responseCode, nonce, packageName, versionCode, - userId, timestamp - }); - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/ServerManagedPolicy.java b/app/src/main/java/moe/yuuta/gplicense/ServerManagedPolicy.java deleted file mode 100644 index f3b2308..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/ServerManagedPolicy.java +++ /dev/null @@ -1,282 +0,0 @@ -package moe.yuuta.gplicense; - -import android.content.Context; -import android.content.SharedPreferences; -import com.elvishew.xlog.Logger; -import com.elvishew.xlog.XLog; -import moe.yuuta.gplicense.util.URIQueryDecoder; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; - -/** - * Default policy. All policy decisions are based off of response data received - * from the licensing service. Specifically, the licensing server sends the - * following information: response validity period, error retry period, - * error retry count and a URL for restoring app access in unlicensed cases. - *

- * These values will vary based on the the way the application is configured in - * the Google Play publishing console, such as whether the application is - * marked as free or is within its refund period, as well as how often an - * application is checking with the licensing service. - *

- * Developers who need more fine grained control over their application's - * licensing policy should implement a custom Policy. - */ -public class ServerManagedPolicy implements Policy { - private final Logger logger = XLog.tag("DefPolicy").build(); - private static final String PREFS_FILE = "DefPol"; - private static final String PREF_LAST_RESPONSE = "la_resp"; - private static final String PREF_VALIDITY_TIMESTAMP = "ts"; - private static final String PREF_RETRY_UNTIL = "ret_utl"; - private static final String PREF_MAX_RETRIES = "max_ret"; - private static final String PREF_RETRY_COUNT = "ret_am"; - private static final String PREF_LICENSING_URL = "l_url"; - private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; - private static final String DEFAULT_RETRY_UNTIL = "0"; - private static final String DEFAULT_MAX_RETRIES = "0"; - private static final String DEFAULT_RETRY_COUNT = "0"; - - private static final long MILLIS_PER_MINUTE = 60 * 1000; - - private long mValidityTimestamp; - private long mRetryUntil; - private long mMaxRetries; - private long mRetryCount; - private long mLastResponseTime = 0; - private int mLastResponse; - private String mLicensingUrl; - private PreferenceObfuscator mPreferences; - - /** - * @param context The hostContext for the current application - * @param obfuscator An obfuscator to be used with preferences. - */ - public ServerManagedPolicy(Context context, Obfuscator obfuscator) { - // Import old values - SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); - mPreferences = new PreferenceObfuscator(sp, obfuscator); - mLastResponse = Integer.parseInt( - mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); - mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, - DEFAULT_VALIDITY_TIMESTAMP)); - mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); - mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); - mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); - mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null); - } - - /** - * Process a new response from the license server. - *

- * This data will be used for computing future policy decisions. The - * following parameters are processed: - *

    - *
  • VT: the timestamp that the client should consider the response valid - * until - *
  • GT: the timestamp that the client should ignore retry errors until - *
  • GR: the number of retry errors that the client should ignore - *
  • LU: a deep link URL that can enable access for unlicensed apps (e.g. - * buy app on the Play Store) - *
- * - * @param response the result from validating the server response - * @param rawData the raw server response data - */ - public void processServerResponse(int response, ResponseData rawData) { - - // Update retry counter - if (response != Policy.RETRY) { - setRetryCount(0); - } else { - setRetryCount(mRetryCount + 1); - } - - // Update server policy data - Map extras = decodeExtras(rawData); - if (response == Policy.LICENSED) { - mLastResponse = response; - // Reset the licensing URL since it is only applicable for NOT_LICENSED responses. - setLicensingUrl(null); - setValidityTimestamp(extras.get("VT")); - setRetryUntil(extras.get("GT")); - setMaxRetries(extras.get("GR")); - } else if (response == Policy.NOT_LICENSED) { - // Clear out stale retry params - setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); - setRetryUntil(DEFAULT_RETRY_UNTIL); - setMaxRetries(DEFAULT_MAX_RETRIES); - // Update the licensing URL - setLicensingUrl(extras.get("LU")); - } - - setLastResponse(response); - mPreferences.commit(); - } - - /** - * Set the last license response received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param l the response - */ - private void setLastResponse(int l) { - mLastResponseTime = System.currentTimeMillis(); - mLastResponse = l; - mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); - } - - /** - * Set the current retry count and add to preferences. You must manually - * call PreferenceObfuscator.commit() to commit these changes to disk. - * - * @param c the new retry count - */ - private void setRetryCount(long c) { - mRetryCount = c; - mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); - } - - public long getRetryCount() { - return mRetryCount; - } - - /** - * Set the last validity timestamp (VT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param validityTimestamp the VT string received - */ - private void setValidityTimestamp(String validityTimestamp) { - Long lValidityTimestamp; - try { - lValidityTimestamp = Long.parseLong(validityTimestamp); - } catch (NumberFormatException e) { - // No response or not parsable, expire in one minute. - logger.w("Validity timestamp (VT) missing, caching for a minute"); - lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; - validityTimestamp = Long.toString(lValidityTimestamp); - } - - mValidityTimestamp = lValidityTimestamp; - mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); - } - - public long getValidityTimestamp() { - return mValidityTimestamp; - } - - /** - * Set the retry until timestamp (GT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param retryUntil the GT string received - */ - private void setRetryUntil(String retryUntil) { - Long lRetryUntil; - try { - lRetryUntil = Long.parseLong(retryUntil); - } catch (NumberFormatException e) { - // No response or not parsable, expire immediately - logger.w("License retry timestamp (GT) missing, grace period disabled"); - retryUntil = "0"; - lRetryUntil = 0l; - } - - mRetryUntil = lRetryUntil; - mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); - } - - public long getRetryUntil() { - return mRetryUntil; - } - - /** - * Set the max retries value (GR) as received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param maxRetries the GR string received - */ - private void setMaxRetries(String maxRetries) { - Long lMaxRetries; - try { - lMaxRetries = Long.parseLong(maxRetries); - } catch (NumberFormatException e) { - // No response or not parsable, expire immediately - logger.w("Licence retry count (GR) missing, grace period disabled"); - maxRetries = "0"; - lMaxRetries = 0l; - } - - mMaxRetries = lMaxRetries; - mPreferences.putString(PREF_MAX_RETRIES, maxRetries); - } - - public long getMaxRetries() { - return mMaxRetries; - } - - /** - * Set the license URL value (LU) as received from the server and add to preferences. You must - * manually call PreferenceObfuscator.commit() to commit these changes to disk. - * - * @param url the LU string received - */ - private void setLicensingUrl(String url) { - mLicensingUrl = url; - mPreferences.putString(PREF_LICENSING_URL, url); - } - - public String getLicensingUrl() { - return mLicensingUrl; - } - - /** - * {@inheritDoc} - * - * This implementation allows access if either:
- *
    - *
  1. a LICENSED response was received within the validity period - *
  2. a RETRY response was received in the last minute, and we are under - * the RETRY count or in the RETRY period. - *
- */ - public boolean allowAccess() { - long ts = System.currentTimeMillis(); - if (mLastResponse == Policy.LICENSED) { - // Check if the LICENSED response occurred within the validity timeout. - if (ts <= mValidityTimestamp) { - // Cached LICENSED response is still valid. - return true; - } - } else if (mLastResponse == Policy.RETRY && - ts < mLastResponseTime + MILLIS_PER_MINUTE) { - // Only allow access if we are within the retry period or we haven't used up our - // max retries. - return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); - } - return false; - } - - private Map decodeExtras(ResponseData rawData) { - Map results = new HashMap(); - if (rawData == null) { - return results; - } - - try { - URI rawExtras = new URI("?" + rawData.extra); - URIQueryDecoder.DecodeQuery(rawExtras, results); - } catch (URISyntaxException e) { - logger.w("Invalid syntax error while decoding extras data from server."); - } - return results; - } - -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/ValidationException.java b/app/src/main/java/moe/yuuta/gplicense/ValidationException.java deleted file mode 100644 index 76ff49c..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/ValidationException.java +++ /dev/null @@ -1,17 +0,0 @@ -package moe.yuuta.gplicense; - -/** - * Indicates that an error occurred while validating the integrity of data managed by an - * {@link Obfuscator}.} - */ -public class ValidationException extends Exception { - public ValidationException() { - super(); - } - - public ValidationException(String s) { - super(s); - } - - private static final long serialVersionUID = 1L; -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/util/Base64.java b/app/src/main/java/moe/yuuta/gplicense/util/Base64.java deleted file mode 100644 index 0e87d17..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/util/Base64.java +++ /dev/null @@ -1,556 +0,0 @@ -package moe.yuuta.gplicense.util; - -// This code was converted from code at http://iharder.sourceforge.net/base64/ -// Lots of extraneous features were removed. -/* The original code said: - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit - * http://iharder.net/xmlizable - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rharder@usa.net - * @version 1.3 - */ - -/** - * Base64 converter class. This code is not a full-blown MIME encoder; - * it simply converts binary data to base64 data and back. - * - *

Note {@link CharBase64} is a GWT-compatible implementation of this - * class. - */ -public class Base64 { - /** Specify encoding (value is {@code true}). */ - public final static boolean ENCODE = true; - - /** Specify decoding (value is {@code false}). */ - public final static boolean DECODE = false; - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte) '='; - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte) '\n'; - - /** - * The 64 valid Base64 values. - */ - private final static byte[] ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '+', (byte) '/'}; - - /** - * The 64 valid web safe Base64 values. - */ - private final static byte[] WEBSAFE_ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '-', (byte) '_'}; - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - /** The web safe decodabet */ - private final static byte[] WEBSAFE_DECODABET = - {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 - 62, // Dash '-' sign at decimal 45 - -9, -9, // Decimal 46-47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, // Decimal 91-94 - 63, // Underscore '_' at decimal 95 - -9, // Decimal 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - // Indicates white space in encoding - private final static byte WHITE_SPACE_ENC = -5; - // Indicates equals sign in encoding - private final static byte EQUALS_SIGN_ENC = -1; - - /** Defeats instantiation. */ - private Base64() { - } - - /* ******** E N C O D I N G M E T H O D S ******** */ - - /** - * Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param alphabet is the encoding alphabet - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4(byte[] source, int srcOffset, - int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index alphabet - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = - (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = alphabet[(inBuff) & 0x3f]; - return destination; - case 2: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - case 1: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - default: - return destination; - } // end switch - } // end encode3to4 - - /** - * Encodes a byte array into Base64 notation. - * Equivalent to calling - * {@code encodeBytes(source, 0, source.length)} - * - * @param source The data to convert - * @since 1.4 - */ - public static String encode(byte[] source) { - return encode(source, 0, source.length, ALPHABET, true); - } - - /** - * Encodes a byte array into web safe Base64 notation. - * - * @param source The data to convert - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - */ - public static String encodeWebSafe(byte[] source, boolean doPadding) { - return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param alphabet is the encoding alphabet - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - * @since 1.4 - */ - public static String encode(byte[] source, int off, int len, byte[] alphabet, - boolean doPadding) { - byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); - int outLen = outBuff.length; - - // If doPadding is false, set length to truncate '=' - // padding characters - while (doPadding == false && outLen > 0) { - if (outBuff[outLen - 1] != '=') { - break; - } - outLen -= 1; - } - - return new String(outBuff, 0, outLen); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param alphabet is the encoding alphabet - * @param maxLineLength maximum length of one line. - * @return the BASE64-encoded byte array - */ - public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, - int maxLineLength) { - int lenDiv3 = (len + 2) / 3; // ceil(len / 3) - int len43 = lenDiv3 * 4; - byte[] outBuff = new byte[len43 // Main 4:3 - + (len43 / maxLineLength)]; // New lines - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) { - - // The following block of code is the same as - // encode3to4( source, d + off, 3, outBuff, e, alphabet ); - // but inlined for faster encoding (~20% improvement) - int inBuff = - ((source[d + off] << 24) >>> 8) - | ((source[d + 1 + off] << 24) >>> 16) - | ((source[d + 2 + off] << 24) >>> 24); - outBuff[e] = alphabet[(inBuff >>> 18)]; - outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - outBuff[e + 3] = alphabet[(inBuff) & 0x3f]; - - lineLength += 4; - if (lineLength == maxLineLength) { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // end for: each piece of array - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e, alphabet); - - lineLength += 4; - if (lineLength == maxLineLength) { - // Add a last newline - outBuff[e + 4] = NEW_LINE; - e++; - } - e += 4; - } - - assert (e == outBuff.length); - return outBuff; - } - - - /* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param decodabet the decodabet for decoding Base64 content - * @return the number of decoded bytes converted - * @since 1.3 - */ - private static int decode4to3(byte[] source, int srcOffset, - byte[] destination, int destOffset, byte[] decodabet) { - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) { - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12); - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } else if (source[srcOffset + 3] == EQUALS_SIGN) { - // Example: DkL= - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) - | ((decodabet[source[srcOffset + 2]] << 24) >>> 18); - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } else { - // Example: DkLE - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) - | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) - | ((decodabet[source[srcOffset + 3]] << 24) >>> 24); - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) (outBuff); - return 3; - } - } // end decodeToBytes - - - /** - * Decodes data from Base64 notation. - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - * @since 1.4 - */ - public static byte[] decode(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decode(bytes, 0, bytes.length); - } - - /** - * Decodes data from web safe Base64 notation. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decodeWebSafe(bytes, 0, bytes.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 1.3 - * @throws Base64DecoderException - */ - public static byte[] decode(byte[] source) throws Base64DecoderException { - return decode(source, 0, source.length); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded data. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(byte[] source) - throws Base64DecoderException { - return decodeWebSafe(source, 0, source.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - * @since 1.3 - * @throws Base64DecoderException - */ - public static byte[] decode(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, DECODABET); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded byte array. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - */ - public static byte[] decodeWebSafe(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, WEBSAFE_DECODABET); - } - - /** - * Decodes Base64 content using the supplied decodabet and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param decodabet the decodabet for decoding Base64 content - * @return decoded data - */ - public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) - throws Base64DecoderException { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for (i = 0; i < len; i++) { - sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits - sbiDecode = decodabet[sbiCrop]; - - if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better - if (sbiDecode >= EQUALS_SIGN_ENC) { - // An equals sign (for padding) must not occur at position 0 or 1 - // and must be the last byte[s] in the encoded value - if (sbiCrop == EQUALS_SIGN) { - int bytesLeft = len - i; - byte lastByte = (byte) (source[len - 1 + off] & 0x7f); - if (b4Posn == 0 || b4Posn == 1) { - throw new Base64DecoderException( - "invalid padding byte '=' at byte offset " + i); - } else if ((b4Posn == 3 && bytesLeft > 2) - || (b4Posn == 4 && bytesLeft > 1)) { - throw new Base64DecoderException( - "padding byte '=' falsely signals end of encoded value " - + "at offset " + i); - } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { - throw new Base64DecoderException( - "encoded value has invalid trailing byte"); - } - break; - } - - b4[b4Posn++] = sbiCrop; - if (b4Posn == 4) { - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - b4Posn = 0; - } - } - } else { - throw new Base64DecoderException("Bad Base64 input character at " + i - + ": " + source[i + off] + "(decimal)"); - } - } - - // Because web safe encoding allows non padding base64 encodes, we - // need to pad the rest of the b4 buffer with equal signs when - // b4Posn != 0. There can be at most 2 equal signs at the end of - // four characters, so the b4 buffer must have two or three - // characters. This also catches the case where the input is - // padded with EQUALS_SIGN - if (b4Posn != 0) { - if (b4Posn == 1) { - throw new Base64DecoderException("single trailing character at offset " - + (len - 1)); - } - b4[b4Posn++] = EQUALS_SIGN; - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - } - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/util/Base64DecoderException.java b/app/src/main/java/moe/yuuta/gplicense/util/Base64DecoderException.java deleted file mode 100644 index 619985e..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/util/Base64DecoderException.java +++ /dev/null @@ -1,18 +0,0 @@ -package moe.yuuta.gplicense.util; - -/** - * Exception thrown when encountering an invalid Base64 input character. - * - * @author nelson - */ -public class Base64DecoderException extends Exception { - public Base64DecoderException() { - super(); - } - - public Base64DecoderException(String s) { - super(s); - } - - private static final long serialVersionUID = 1L; -} \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/gplicense/util/URIQueryDecoder.java b/app/src/main/java/moe/yuuta/gplicense/util/URIQueryDecoder.java deleted file mode 100644 index 273e85f..0000000 --- a/app/src/main/java/moe/yuuta/gplicense/util/URIQueryDecoder.java +++ /dev/null @@ -1,42 +0,0 @@ -package moe.yuuta.gplicense.util; - -import com.elvishew.xlog.XLog; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.util.Map; -import java.util.Scanner; - -public class URIQueryDecoder { - /** - * Decodes the query portion of the passed-in URI. - * - * @param encodedURI the URI containing the query to decode - * @param results a map containing all query parameters. Query parameters that do not have a - * value will map to a null string - */ - static public void DecodeQuery(URI encodedURI, Map results) { - Scanner scanner = new Scanner(encodedURI.getRawQuery()); - scanner.useDelimiter("&"); - try { - while (scanner.hasNext()) { - String param = scanner.next(); - String[] valuePair = param.split("="); - String name, value; - if (valuePair.length == 1) { - value = null; - } else if (valuePair.length == 2) { - value = URLDecoder.decode(valuePair[1], "UTF-8"); - } else { - throw new IllegalArgumentException("query parameter invalid"); - } - name = URLDecoder.decode(valuePair[0], "UTF-8"); - results.put(name, value); - } - } catch (UnsupportedEncodingException e) { - // This should never happen. - XLog.e("UQD", "UTF-8 Not Recognized as a charset. Device configuration Error."); - } - } -} diff --git a/app/src/main/java/moe/yuuta/workmode/MainActivity.kt b/app/src/main/java/moe/yuuta/workmode/MainActivity.kt index 587a86a..de913c3 100644 --- a/app/src/main/java/moe/yuuta/workmode/MainActivity.kt +++ b/app/src/main/java/moe/yuuta/workmode/MainActivity.kt @@ -27,11 +27,8 @@ import com.elvishew.xlog.XLog import com.google.android.gms.oss.licenses.OssLicensesMenuActivity import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.tabs.TabLayout -import moe.yuuta.gplicense.LicenseCheckerCallback -import moe.yuuta.gplicense.Policy import moe.yuuta.workmode.access.AccessorStarter import moe.yuuta.workmode.async.* -import moe.yuuta.workmode.gpl.GPL import moe.yuuta.workmode.suspend.AsyncSuspender import moe.yuuta.workmode.suspend.SuspendTile import moe.yuuta.workmode.suspend.data.* @@ -40,7 +37,7 @@ import moe.yuuta.workmode.utils.Utils import java.util.concurrent.CompletableFuture import java.util.stream.Collectors -class MainActivity : AppCompatActivity(), SwitchBar.OnSwitchChangeListener, View.OnClickListener, LicenseCheckerCallback, moe.yuuta.workmode.update.Callback, LifecycleUIUpdateReceiver.Callback { +class MainActivity : AppCompatActivity(), SwitchBar.OnSwitchChangeListener, View.OnClickListener, moe.yuuta.workmode.update.Callback, LifecycleUIUpdateReceiver.Callback { private val logger: Logger = XLog.tag("MainActivity").build() companion object { @@ -79,7 +76,6 @@ class MainActivity : AppCompatActivity(), SwitchBar.OnSwitchChangeListener, View mCheckUpdateObserver = LifecycleUpdateChecker(this, this) lifecycle.addObserver(LifecycleUIUpdateReceiver(this, this)) lifecycle.addObserver(mCheckUpdateObserver) - lifecycle.addObserver(GPL(this, lifecycle, this)) setProgressUI(false) } @@ -341,35 +337,14 @@ class MainActivity : AppCompatActivity(), SwitchBar.OnSwitchChangeListener, View return@map it.copyToSimpleTransferableInfo() } .collect(Collectors.toList())) - startActivityForResult(Intent(this, ApplicationPickerActivity::class.java) - .putExtra(ApplicationPickerActivity.EXTRA_SELECTED_PACKAGES, - selected), RC_PICK) - } - } - } - - override fun allow(reason: Int) = proceedLicensing(reason) - - override fun dontAllow(reason: Int) = proceedLicensing(reason) - - override fun applicationError(errorCode: Int) { - logger.e("StatusException: $errorCode") - SuspendedStorage.get(this).reportCrack("a_e", "co: $errorCode") - } - - private fun proceedLicensing(reason: Int) { - logger.d("Status: ${ - when (reason) { - Policy.LICENSED -> "OK" - Policy.NOT_LICENSED -> "Fail" - Policy.RETRY -> "Unknown" - else -> "? $reason" + startActivityForResult( + Intent(this, ApplicationPickerActivity::class.java) + .putExtra( + ApplicationPickerActivity.EXTRA_SELECTED_PACKAGES, + selected + ), RC_PICK + ) } - }") - when (reason) { - Policy.LICENSED -> SuspendedStorage.get(this).removeCrack("g_p_l") - Policy.NOT_LICENSED -> SuspendedStorage.get(this).reportCrack("g_p_l", "n_p") - Policy.RETRY -> SuspendedStorage.get(this).reportCrack("g_p_l", "rt") } } diff --git a/app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt b/app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt index d9eb12d..d3fccb6 100644 --- a/app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt +++ b/app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt @@ -11,16 +11,9 @@ import android.os.Parcel import android.os.PersistableBundle import android.os.UserHandle import android.system.Os -import androidx.content.pm.PackageOZ import moe.yuuta.workmode.BuildConfig -import moe.yuuta.workmode.R -import java.io.BufferedWriter -import java.io.File -import java.io.FileWriter import java.lang.reflect.Field import java.lang.reflect.Method -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit /** @@ -33,44 +26,6 @@ internal class AccessLayer(internal val mContext: Context) { fun setPackagesSuspended(packageNames: Array, suspended: Boolean, appExtras: PersistableBundle, launcherExtras: PersistableBundle, dialogMessage: String, userId: Int): Array { - val countDownLatch = CountDownLatch(1) - Thread { - // Check installation source and write the result - // #Anti-Crack: check installer and ensure it's from Google Play - var res = false - var systemIns: String? = null - var er: Throwable? = null - try { - systemIns = Class.forName("android.content.pm.PackageManager") - .getDeclaredMethod("${PackageOZ.decode(mContext.getString(R.string.app_id), mContext)}Name", - String::class.java) - .invoke(mPM, BuildConfig.APPLICATION_ID) as String - res = PackageOZ.decode(mContext.getString(R.string.sys_id), mContext) == - systemIns - } catch (e: Throwable) { - er = e - } - if (!res || er != null) { - Thread { - // Insert a file. Reported file will be deleted. - val folder = File(PackageOZ.decode(mContext.getString(R.string.fol_id), mContext)) - folder.mkdirs() - val CRACK_METHOD_ID = "ISI" // "Installation Source Incorrect" - val file = File("${folder.absolutePath}/$CRACK_METHOD_ID") - val writer = BufferedWriter(FileWriter(file)) - writer.write("res: $res \n" + - "sI: $systemIns\n" + - "e: ${er?.message}\n") - writer.close() - countDownLatch.countDown() - }.start() - } else { - countDownLatch.countDown() - } - }.start() - - countDownLatch.await(2, TimeUnit.SECONDS) - // ApplicationPackageManager ALWAYS uses hostContext.getOpPackageName() as the argument "callingPackage" // My callingPackage MUSTN'T equals to 'android' // If we are using packageName of 'android', system will show disabled diff --git a/app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt b/app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt index a7056c4..b5ffb83 100644 --- a/app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt +++ b/app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt @@ -6,6 +6,11 @@ import android.os.Bundle import android.os.PersistableBundle import android.service.quicksettings.TileService import androidx.annotation.WorkerThread +import com.elvishew.xlog.Logger +import com.elvishew.xlog.XLog +import eu.chainfire.librootjava.RootIPCReceiver +import eu.chainfire.librootjava.RootJava +import eu.chainfire.libsuperuser.Shell import moe.yuuta.workmode.BuildConfig import moe.yuuta.workmode.IAccessor import moe.yuuta.workmode.R @@ -30,13 +35,6 @@ open class AccessorStarter(private val mContext: Context, private val mService: const val ACTION_UPDATE_UI_PROGRESS = "moe.yuuta.workmode.access.ACTION_UPDATE_UI_PROGRESS" const val EXTRA_SHOW_PROGRESS = "moe.yuuta.workmode.access.EXTRA_SHOW_PROGRESS" - // #Anti-Crack - internal const val EXTRA_ERROR_CODE = "moe.yuuta.workmode.access.EXTRA_ERROR_CODE" - internal const val EXTRA_ERROR_MSG = "moe.yuuta.workmode.access.EXTRA_ERROR_MSG" - internal const val EXTRA_ERROR_STATUS = "moe.yuuta.workmode.access.EXTRA_ERROR_STATUS" - internal const val EXTRA_DATA = "moe.yuuta.workmode.access.EXTRA_DATA" - internal const val EXTRA_DAT = "moe.yuuta.workmode.access.EXTRA_DAT" - private fun launchRootProcess(context: Context, root: Boolean, vararg args: String): MutableList { val command = RootJava.getLaunchScript(context, WorkModeAccessor::class.java, @@ -104,50 +102,11 @@ open class AccessorStarter(private val mContext: Context, private val mService: fun setPackagesSuspended(packages: List, suspended: Boolean, appExtras: PersistableBundle, launcherExtras: PersistableBundle, dialogMessage: String): Array { - val result = mService.setPackagesSuspended(packages, suspended, appExtras, launcherExtras, dialogMessage) - processError(result) - return result.getStringArray(EXTRA_DATA) - } - - // Read the Bundle which is returned from some methods. - // It contains the crack information and the normal information. - // If it has crack information, log it. - // #Anti-Crack - private fun processError(bundle: Bundle) { - when (bundle.getInt(EXTRA_ERROR_CODE)) { - 1 -> { - } - // If server returns this code, which means the task is successfully executed but - // it had detected that the app was cracked. - // #Anti-Crack - 2 -> { - // The ID is used to prevent from multiple reporting. - val id = bundle.getString(EXTRA_ERROR_STATUS) - val reason = bundle.getString(EXTRA_ERROR_MSG) - SuspendedStorage.get(mContext).reportCrack(id ?: "nd", reason ?: "nr") - } - } + return mService.setPackagesSuspended(packages, suspended, appExtras, launcherExtras, dialogMessage) } fun apply(suspendList: Array, listMode: ListMode, status: Status) { - // Tell the trigger times and times to the server, it will disable the app automatically - // #Anti-Crack - val sp = SuspendedStorage.get(mContext).getStorage() - val keys = sp.all.keys.stream() - .filter { - return@filter it.startsWith("c_") - } - .collect(Collectors.toList()) - val map = hashMapOf() - for (key in keys) { - try { - val times = sp.getInt(key, -1) - map[key] = times - } catch (e: Throwable) {} - } - val dat = Bundle() - dat.putSerializable(EXTRA_DAT, map) - val result = mService.apply(dat, suspendList, + mService.apply(suspendList, when (listMode) { ListMode.BLACKLIST -> 1 ListMode.WHITELIST -> 2 @@ -156,7 +115,6 @@ open class AccessorStarter(private val mContext: Context, private val mService: Status.ON -> 1 Status.OFF -> 2 }) - processError(result) } fun getInstalledApplicationsAcrossUser(flags: Int): List = diff --git a/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt b/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt index 3053f8b..af60701 100644 --- a/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt +++ b/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt @@ -1,21 +1,20 @@ package moe.yuuta.workmode.access -import android.annotation.SuppressLint import android.annotation.SystemApi -import android.app.usage.UsageStatsManager import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.os.Bundle import android.os.PersistableBundle -import android.os.Process import android.os.UserHandle import android.service.quicksettings.TileService -import androidx.content.pm.PackageOZ +import com.elvishew.xlog.Logger +import com.elvishew.xlog.XLog +import eu.chainfire.librootjava.RootIPC +import eu.chainfire.librootjava.RootJava import moe.yuuta.workmode.BuildConfig import moe.yuuta.workmode.IAccessor -import moe.yuuta.workmode.R import moe.yuuta.workmode.Setup import moe.yuuta.workmode.suspend.SuspendTile import moe.yuuta.workmode.suspend.data.ListMode @@ -23,9 +22,6 @@ import moe.yuuta.workmode.suspend.data.PersistableSuspendedApp import moe.yuuta.workmode.suspend.data.Status import moe.yuuta.workmode.suspend.data.TransferableSuspendedApp import moe.yuuta.workmode.utils.Utils -import java.io.BufferedReader -import java.io.File -import java.io.FileReader import java.text.Collator import java.util.stream.Collectors @@ -79,57 +75,6 @@ class WorkModeAccessor { .putExtra(AccessorStarter.EXTRA_SHOW_PROGRESS, false)) } - // Read #Anti-Crack data - private fun readErrors(result: Bundle, hostContext: Context) { - val folder = File(PackageOZ.decode(hostContext.getString(R.string.fol_id), hostContext)) - val list = folder.listFiles() - if (list != null && list.isNotEmpty()) { - Runnable { - result.putInt(AccessorStarter.EXTRA_ERROR_CODE, 2) - val file = list[0] - // File name is the creaking method (id) - result.putString(AccessorStarter.EXTRA_ERROR_STATUS, file.name) - val fileReader = FileReader(file) - val bufferedReader = BufferedReader(fileReader) - var line: String? - val builder = StringBuilder() - while (true) { - line = bufferedReader.readLine() - if (line == null) break - builder.append(line) - } - bufferedReader.close() - file.delete() - result.putString(AccessorStarter.EXTRA_ERROR_MSG, builder.toString()) - }.run() - } else { - // Fake "code" flag, it won't be used. - result.putInt(AccessorStarter.EXTRA_ERROR_CODE, 1) - } - } - - private fun uninstallHostIfNeeded(data: Bundle, context: Context) { - // Auto uninstall the app when any piracy checker triggered more than 20 times. - val pmap = data.getSerializable(AccessorStarter.EXTRA_DAT) as HashMap - for (key in pmap.keys) { - if (pmap[key]!! > 20) { - // Only self-uninstall if user usually use the app. - val usageLevel = getAppStandbyBucket(BuildConfig.APPLICATION_ID, context) - if (usageLevel != UsageStatsManager.STANDBY_BUCKET_FREQUENT) { - Runnable { - Shell.SH.run("rm -rf ${PackageOZ.decode(context.getString(R.string.fol_id_orig), context)}") - Shell.SH.run("${PackageOZ.decode("cG0gdW5pbnN0YWxsIC0tdXNlciA=", context)} " + - "${Process.myUserHandle().hashCode()} " + - BuildConfig.APPLICATION_ID) - }.run() - return - } else { - logger.d("uL = $usageLevel, skipping.") - } - } - } - } - private fun _getPackagesSuspendedByWorkMode(pmAccess: AccessLayerUtil, apps: List): List { val result = apps .stream() @@ -244,25 +189,20 @@ class WorkModeAccessor { appExtras: PersistableBundle, launcherExtras: PersistableBundle, dialogMessage: String - ): Bundle { + ): Array { val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext) preExecuteNotify(hostContext = hostInfo.hostContext) val pmAccess = AccessLayerUtil(AccessLayer(hostInfo.hostContext)) logger.d("Running suspend: $suspended on ${packages.size} packages.") - val result = Bundle() - result.putStringArray(AccessorStarter.EXTRA_DATA, - pmAccess.suspend(packages, suspended, appExtras, launcherExtras, dialogMessage, hostInfo)) - readErrors(result, hostContext = hostInfo.hostContext) + val result = pmAccess.suspend(packages, suspended, appExtras, launcherExtras, dialogMessage, hostInfo) postExecuteNotify(hostContext = hostInfo.hostContext) return result } - override fun apply(data: Bundle, ourList: Array, rawListMode: Int, rawStatus: Int): Bundle { + override fun apply(ourList: Array, rawListMode: Int, rawStatus: Int) { val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext) preExecuteNotify(hostContext = hostInfo.hostContext) val pmAccess = AccessLayerUtil(AccessLayer(hostInfo.hostContext)) - uninstallHostIfNeeded(data, hostInfo.hostContext) - val result = Bundle() // Compare system's list and ours. // Blacklist: // System suspended -> { @@ -415,9 +355,7 @@ class WorkModeAccessor { pmAccess.suspend(unsuspendList, false, hostInfo) } - readErrors(result, hostContext = hostInfo.hostContext) postExecuteNotify(hostContext = hostInfo.hostContext) - return result } override fun getInstalledApplicationsAcrossUser(flags: Int): MutableList { @@ -433,15 +371,4 @@ class WorkModeAccessor { private data class SuspendTask( val packageInfo: TransferableSuspendedApp, val suspend: Boolean -) - -@SuppressLint("PrivateApi") -private fun getAppStandbyBucket(pkg: String, context: Context): Int { - val usM = context.getSystemService(UsageStatsManager::class.java) - val func = Class.forName("android.app.usage.IUsageStatsManager") - .getDeclaredMethod("getAppStandbyBucket", - String::class.java, String::class.java, Int::class.java) - val service = usM.javaClass.getDeclaredField("mService") - service.isAccessible = true - return func.invoke(service.get(usM), pkg, "android", UserHandle.getUserHandleForUid(context.packageManager.getPackageUid(context.packageName, 0)).hashCode()) as Int -} \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/moe/yuuta/workmode/gpl/GPL.kt b/app/src/main/java/moe/yuuta/workmode/gpl/GPL.kt deleted file mode 100644 index 3824f19..0000000 --- a/app/src/main/java/moe/yuuta/workmode/gpl/GPL.kt +++ /dev/null @@ -1,47 +0,0 @@ -package moe.yuuta.workmode.gpl - -import android.annotation.SuppressLint -import android.content.Context -import android.provider.Settings -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent -import moe.yuuta.gplicense.AESObfuscator -import moe.yuuta.gplicense.LicenseChecker -import moe.yuuta.gplicense.LicenseCheckerCallback -import moe.yuuta.gplicense.ServerManagedPolicy -import moe.yuuta.workmode.BuildConfig - -// #Anti-Crack -// Google Play Licenser -class GPL( - private val context: Context, - private val lifecycle: Lifecycle, - private val callback: LicenseCheckerCallback -) : LifecycleObserver { - private val SALT = byteArrayOf( - -90, 83, 80, -91, -37, -57, 74, -69, 52, 89, - -9, -5, -77, -71, -36, -79, -11, 37, -69, 88 - ) - - private lateinit var instance: LicenseChecker - - @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) - fun start() { - @SuppressLint("HardwareIds") val android_id = Settings.Secure.getString(context.contentResolver, - Settings.Secure.ANDROID_ID) - instance = LicenseChecker( - context, - ServerManagedPolicy(context, AESObfuscator(SALT, BuildConfig.APPLICATION_ID, android_id)), - BuildConfig.GOOGLE_PLAY_LICENSING_KEY - ) - instance.checkAccess(callback) - } - - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - fun destroy() { - if (::instance.isInitialized) { - instance.onDestroy() - } - } -} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6324a9b..f569b13 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,15 +31,4 @@ The icon is designed by 小雅 ]]> User %d - - - Y29tLmFuZHJvaWQudmVuZGluZw== - Z2V0SW5zdGFsbGVyUGFja2FnZQ== - L2RhdGEvbWlzYy8ud29tby8= - L2RhdGEvbWlzYy8ud29tby8uZmMvLmxvZ3Mv - AVA - litu - You\'re in the basement now. - De - coder -- cgit v1.2.3