aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuutaW <17158086+trumeet@users.noreply.github.com>2019-05-16 16:03:24 -0700
committerYuutaW <17158086+Trumeet@users.noreply.github.com>2019-05-16 16:03:24 -0700
commit1ae04c3850eb876c7f4d1956089741e747691b3a (patch)
tree0f6f2e105fe170613d9eac6d2bdec055b5cf0a8a
parent757fe89c745fc404259bda6653c046c13a4e617f (diff)
downloadWorkMode-1ae04c3850eb876c7f4d1956089741e747691b3a.tar
WorkMode-1ae04c3850eb876c7f4d1956089741e747691b3a.tar.gz
WorkMode-1ae04c3850eb876c7f4d1956089741e747691b3a.tar.bz2
WorkMode-1ae04c3850eb876c7f4d1956089741e747691b3a.zip
refactor: remove #Anti-Crack
Signed-off-by: YuutaW <17158086+Trumeet@users.noreply.github.com>
-rw-r--r--app/src/main/aidl/moe/yuuta/workmode/IAccessor.aidl4
-rw-r--r--app/src/main/java/androidx/content/pm/PackageOZ.java54
-rw-r--r--app/src/main/java/moe/yuuta/ext/ConnCallback.java9
-rw-r--r--app/src/main/java/moe/yuuta/ext/IPCResultListener.java15
-rw-r--r--app/src/main/java/moe/yuuta/ext/IResultListener.java102
-rw-r--r--app/src/main/java/moe/yuuta/ext/IService.java102
-rw-r--r--app/src/main/java/moe/yuuta/ext/LicServiceConn.java23
-rw-r--r--app/src/main/java/moe/yuuta/ext/ResultCallback.java6
-rw-r--r--app/src/main/java/moe/yuuta/ext/package-info.java4
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java94
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/DeviceLimiter.java31
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/LicenseChecker.java356
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/LicenseCheckerCallback.java51
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/LicenseValidator.java219
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/NullDeviceLimiter.java16
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/Obfuscator.java32
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/Policy.java49
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/PreferenceObfuscator.java63
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/ResponseData.java65
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/ServerManagedPolicy.java282
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/ValidationException.java17
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/util/Base64.java556
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/util/Base64DecoderException.java18
-rw-r--r--app/src/main/java/moe/yuuta/gplicense/util/URIQueryDecoder.java42
-rw-r--r--app/src/main/java/moe/yuuta/workmode/MainActivity.kt41
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt45
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt56
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt89
-rw-r--r--app/src/main/java/moe/yuuta/workmode/gpl/GPL.kt47
-rw-r--r--app/src/main/res/values/strings.xml11
30 files changed, 25 insertions, 2474 deletions
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<TransferableSuspendedApp> packages,
+ String[] setPackagesSuspended(in List<TransferableSuspendedApp> 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<TransferableSuspendedApp> 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.
- * <p>
- * The LICENSED response from the server contains a user identifier unique to
- * the &lt;application, user&gt; 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.
- * <p>
- * The server can look at the &lt;application, user, device id&gt; 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.
- * <p>
- * 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.
- * <p>
- * 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<LicenseValidator> mChecksInProgress = new HashSet<LicenseValidator>();
- private final Queue<LicenseValidator> mPendingChecks = new LinkedList<LicenseValidator>();
-
- 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.
- * <p>
- * 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.
- * <p>
- * source string: "com.android.vending.licensing.IService"
- * <p>
- *
- * @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.
- * <p>
- * 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.
- * <p>
- * Upon checking with the Market server and conferring with the {@link Policy},
- * the library calls the appropriate callback method to communicate the result.
- * <p>
- * <b>The callback does not occur in the original checking thread.</b> Your
- * application should post to the appropriate handling thread or lock
- * accordingly.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * This data will be used for computing future policy decisions. The
- * following parameters are processed:
- * <ul>
- * <li>VT: the timestamp that the client should consider the response valid
- * until
- * <li>GT: the timestamp that the client should ignore retry errors until
- * <li>GR: the number of retry errors that the client should ignore
- * <li>LU: a deep link URL that can enable access for unlicensed apps (e.g.
- * buy app on the Play Store)
- * </ul>
- *
- * @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<String, String> 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:<br>
- * <ol>
- * <li>a LICENSED response was received within the validity period
- * <li>a RETRY response was received in the last minute, and we are under
- * the RETRY count or in the RETRY period.
- * </ol>
- */
- 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<String, String> decodeExtras(ResponseData rawData) {
- Map<String, String> results = new HashMap<String, String>();
- 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:
- * <p>
- * 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
- * <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
- * periodically to check for updates or to contribute improvements.
- * </p>
- *
- * @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.
- *
- * <p>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 <var>source</var>
- * and writes the resulting four Base64 bytes to <var>destination</var>.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * <var>srcOffset</var> and <var>destOffset</var>.
- * This method does not check to make sure your arrays
- * are large enough to accommodate <var>srcOffset</var> + 3 for
- * the <var>source</var> array or <var>destOffset</var> + 4 for
- * the <var>destination</var> array.
- * The actual number of significant bytes in your array is
- * given by <var>numSigBytes</var>.
- *
- * @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 <var>destination</var> 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 <var>source</var>
- * and writes the resulting bytes (up to three of them)
- * to <var>destination</var>.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * <var>srcOffset</var> and <var>destOffset</var>.
- * This method does not check to make sure your arrays
- * are large enough to accommodate <var>srcOffset</var> + 4 for
- * the <var>source</var> array or <var>destOffset</var> + 3 for
- * the <var>destination</var> 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<String, String> 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<String>, suspended: Boolean,
appExtras: PersistableBundle, launcherExtras: PersistableBundle,
dialogMessage: String, userId: Int): Array<String> {
- 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<String> {
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<TransferableSuspendedApp>, suspended: Boolean,
appExtras: PersistableBundle, launcherExtras: PersistableBundle,
dialogMessage: String): Array<String> {
- 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<TransferableSuspendedApp>, 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<String, Int>()
- 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<TransferableSuspendedApp> =
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<String, Int>
- 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<TransferableSuspendedApp>): List<TransferableSuspendedApp> {
val result = apps
.stream()
@@ -244,25 +189,20 @@ class WorkModeAccessor {
appExtras: PersistableBundle,
launcherExtras: PersistableBundle,
dialogMessage: String
- ): Bundle {
+ ): Array<String> {
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<out TransferableSuspendedApp>, rawListMode: Int, rawStatus: Int): Bundle {
+ override fun apply(ourList: Array<out TransferableSuspendedApp>, 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<TransferableSuspendedApp> {
@@ -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 <a href="https://twitter.com/xiaoya_er">小雅</a>
]]></string>
<string name="app_list_user_template">User %d</string>
-
- <!-- #Anti-Crack -->
- <string name="sys_id" translatable="false">Y29tLmFuZHJvaWQudmVuZGluZw==</string>
- <string name="app_id" translatable="false">Z2V0SW5zdGFsbGVyUGFja2FnZQ==</string>
- <string name="fol_id_orig" translatable="false">L2RhdGEvbWlzYy8ud29tby8=</string>
- <string name="fol_id" translatable="false">L2RhdGEvbWlzYy8ud29tby8uZmMvLmxvZ3Mv</string>
- <string name="b6_k" translatable="false">AVA</string>
- <string name="b7_a" translatable="false">litu</string>
- <string name="b90_key" translatable="false">You\'re in the basement now.</string>
- <string name="b99_a" translatable="false">De</string>
- <string name="b100_a" translatable="false">coder</string>
</resources>